import { NativeEventEmitter, Platform } from 'react-native'; import FWNavigator from './FWNavigator'; import LiveStream from './LiveStream'; import type AdBadgeConfiguration from './models/AdBadgeConfiguration'; import type { DataTrackingLevel } from './models/DataTrackingLevel'; import { FWEventName } from './models/FWEventName'; import type { CustomCTAClickEvent, SDKInitEvent, VideoFeedClickEvent, VideoPlaybackEvent, } from './models/FWEvents'; import LivestreamPlayerDesignVersion from './models/LivestreamPlayerDesignVersion'; import ShortVideoPlayerDesignVersion from './models/ShortVideoPlayerDesignVersion'; import type MarkInitCalledOptions from './models/MarkInitCalledOptions'; import type OpenVideoPlayerConfiguration from './models/OpenVideoPlayerConfiguration'; import type { OpenVideoPlayerSource } from './models/OpenVideoPlayerSource'; import type { VideoLaunchBehavior } from './models/SDKInitOptions'; import type TrackPurchaseParameters from './models/TrackPurchaseParameters'; import FireworkSDKModule, { FireworkSDKModuleEventEmitter, } from './modules/FireworkSDKModule'; import LiveStreamModule from './modules/LiveStreamModule'; import ShoppingModule from './modules/ShoppingModule'; import FWGlobalState from './utils/FWGlobalState'; import FWLoggerUtil from './utils/FWLoggerUtil'; import TrackPurchaseParametersUtil from './utils/TrackPurchaseParametersUtil'; import VideoShopping from './VideoShopping'; export type SDKInitCallback = (event: SDKInitEvent) => Promise | void; export type CustomCTAClickCallback = ( event: CustomCTAClickEvent ) => Promise | void; export type VideoPlaybackCallback = ( event: VideoPlaybackEvent ) => Promise | void; export type VideoFeedClickCallback = ( event: VideoFeedClickEvent ) => Promise | void; /** * Firework SDK. */ class FireworkSDK { private static _instance?: FireworkSDK; /** * The callback of SDK initialization. */ public onSDKInit?: SDKInitCallback; /** * the callback of clicking Video Feed. */ public get onVideoFeedClick(): VideoFeedClickCallback | undefined { return this._onVideoFeedClick; } public set onVideoFeedClick(value: VideoFeedClickCallback | undefined) { this._onVideoFeedClick = value; if (Platform.OS === 'ios') { FireworkSDKModule.setVideoFeedClickCallbackEnabled(!!value); } } private _onVideoFeedClick?: VideoFeedClickCallback | undefined; /** * The callback of clicking custom CTA. */ public get onCustomCTAClick(): CustomCTAClickCallback | undefined { return this._onCustomCTAClick; } public set onCustomCTAClick(value: CustomCTAClickCallback | undefined) { FWLoggerUtil.log(`Set onCustomCTAClick callback ${!!value}`); this._onCustomCTAClick = value; FireworkSDKModule.setCustomCTAClickEnabled(value ? true : false); } private _onCustomCTAClick: CustomCTAClickCallback | undefined; /** * The callback of video playback. */ public get onVideoPlayback(): VideoPlaybackCallback | undefined { return this._onVideoPlayback; } public set onVideoPlayback(value: VideoPlaybackCallback | undefined) { this._onVideoPlayback = value; FireworkSDKModule.setVideoPlaybackEventEnabled(value ? true : false); } private _onVideoPlayback?: VideoPlaybackCallback | undefined; /** * The global share base URL of the video. * The {@link VideoPlayerConfiguration.shareBaseURL} in {@link VideoPlayerConfiguration} will override this. */ public get shareBaseURL(): string | undefined { return this._shareBaseURL; } public set shareBaseURL(value: string | undefined) { this.updateInternalShareBaseURL(value); } private _shareBaseURL: string | undefined; /** * The configuration for ad badges. */ public get adBadgeConfiguration(): AdBadgeConfiguration | undefined { return this._adBadgeConfiguration; } public set adBadgeConfiguration(value: AdBadgeConfiguration | undefined) { const valueHasChanged = this._adBadgeConfiguration?.badgeTextType !== value?.badgeTextType || this._adBadgeConfiguration?.backgroundColor !== value?.backgroundColor || this._adBadgeConfiguration?.textColor !== value?.textColor || this._adBadgeConfiguration?.androidFontInfo?.isCustom !== value?.androidFontInfo?.isCustom || this._adBadgeConfiguration?.androidFontInfo?.typefaceName !== value?.androidFontInfo?.typefaceName; this._adBadgeConfiguration = value; FireworkSDKModule.setAdBadgeConfiguration(value ?? {}).then(() => { if (valueHasChanged) { this.eventEmitter.emit(FWEventName.AdBadgeConfigurationUpdated); } }); } private _adBadgeConfiguration: AdBadgeConfiguration | undefined; public get appLanguage(): string | undefined | null { return this._appLanguage; } private _appLanguage: string | undefined | null; /** * The configuration for data tracking level. */ public get dataTrackingLevel(): DataTrackingLevel { return this._dataTrackingLevel; } public set dataTrackingLevel(value: DataTrackingLevel) { const valueHasChanged = this._dataTrackingLevel !== value; this._dataTrackingLevel = value; FireworkSDKModule.setDataTrackingLevel(value).then(() => { if (valueHasChanged) { this.eventEmitter.emit(FWEventName.DataTrackingLevelUpdated); } }); } private _dataTrackingLevel: DataTrackingLevel = 'all'; /** * Defaults to false. * You can enable debug logs by setting this property to true. */ public get debugLogsEnabled(): boolean { return FWLoggerUtil.enabled; } public set debugLogsEnabled(value: boolean) { FWLoggerUtil.enabled = value; } /** * Get video launch behavior. */ public get videoLaunchBehavior(): VideoLaunchBehavior | undefined { return this._videoLaunchBehavior; } private _videoLaunchBehavior: VideoLaunchBehavior | undefined; /** * The version of the livestream player design. */ public get livestreamPlayerDesignVersion(): LivestreamPlayerDesignVersion { return this._livestreamPlayerDesignVersion; } public set livestreamPlayerDesignVersion( value: LivestreamPlayerDesignVersion ) { const valueHasChanged = this._livestreamPlayerDesignVersion !== value; this._livestreamPlayerDesignVersion = value; FireworkSDKModule.setLivestreamPlayerDesignVersion(value).then(() => { if (valueHasChanged) { this.eventEmitter.emit( FWEventName.LivestreamPlayerDesignVersionUpdated ); } }); } private _livestreamPlayerDesignVersion: LivestreamPlayerDesignVersion = LivestreamPlayerDesignVersion.v1; /** * The version of the short video player design. */ public get shortVideoPlayerDesignVersion(): ShortVideoPlayerDesignVersion { return this._shortVideoPlayerDesignVersion; } public set shortVideoPlayerDesignVersion( value: ShortVideoPlayerDesignVersion ) { const valueHasChanged = this._shortVideoPlayerDesignVersion !== value; this._shortVideoPlayerDesignVersion = value; FireworkSDKModule.setShortVideoPlayerDesignVersion(value).then(() => { if (valueHasChanged) { this.eventEmitter.emit( FWEventName.ShortVideoPlayerDesignVersionUpdated ); } }); } private _shortVideoPlayerDesignVersion: ShortVideoPlayerDesignVersion = ShortVideoPlayerDesignVersion.v1; /** * When enabled, prevents auto-entering PiP when the user leaves the fullscreen player * (e.g. presses Home or switches to another app). The player activity stays in the * background stack instead of entering PiP mode. * Defaults to false. Only supported on Android. */ public set preventPipOnLeave(prevent: boolean) { this._preventPipOnLeave = prevent; if (Platform.OS === 'android') { FireworkSDKModule.setPreventPipOnLeave(prevent); } } public get preventPipOnLeave(): boolean { return this._preventPipOnLeave; } private _preventPipOnLeave: boolean = false; private get eventEmitter(): NativeEventEmitter { return FireworkSDKModuleEventEmitter; } /** * Get VideoShopping object. */ public get shopping(): VideoShopping { return this._shopping; } private _shopping: VideoShopping = VideoShopping.getInstance(); /** * Get LiveStream object. */ public get liveStream(): LiveStream { return this._liveStream; } private _liveStream: LiveStream = LiveStream.getInstance(); /** * Get FWNavigator object. */ public get navigator(): FWNavigator { return this._navigator; } private _navigator: FWNavigator = FWNavigator.getInstance(); private hasCalledSDKInitCallback: boolean = false; /** * Get global single instance of FireworkSDK class. * @returns FireworkSDK */ public static getInstance() { if (!FireworkSDK._instance) { FireworkSDK._instance = new FireworkSDK(); } return FireworkSDK._instance!; } private constructor() { FWLoggerUtil.log('FireworkSDK constructor'); this.eventEmitter.addListener(FWEventName.SDKInit, (event) => { FWLoggerUtil.log(`Receive SDKInit event ${event?.error}`); FWLoggerUtil.log( `FireworkSDKGlobalState.getInstance().sdkInitSucceeded: ${FWGlobalState.getInstance().sdkInitSucceeded}` ); if (event && !event.error) { this.completeRemainingInitialization(); } if (!this.hasCalledSDKInitCallback) { this.hasCalledSDKInitCallback = true; if (this.onSDKInit) { this.onSDKInit( event ?? { error: { name: 'SDKInitError', reason: 'SDK initialization failed', }, } ); } } }); this.eventEmitter.addListener(FWEventName.LogMessage, (event) => { FWLoggerUtil.logNativeMessage(event.message); }); this.eventEmitter.addListener(FWEventName.CustomCTAClick, (event) => { FWLoggerUtil.log(`Receive CustomCTAClick url: ${event?.url}`); FWLoggerUtil.log( `Receive CustomCTAClick callbackId: ${event?.callbackId}` ); if (this.onCustomCTAClick && event) { if (event.callbackId) { const callbackId = event.callbackId; delete event.callbackId; if (Platform.OS === 'ios') { event.playerHandler = { pause: () => FireworkSDKModule.pausePlayer(callbackId), resume: () => FireworkSDKModule.resumePlayer(callbackId), }; } } this.onCustomCTAClick(event); } }); this.eventEmitter.addListener(FWEventName.VideoPlayback, (event) => { FWLoggerUtil.log( `Receive VideoPlayback event eventName: ${event?.eventName}` ); if (this.onVideoPlayback && event) { this.onVideoPlayback(event); } }); this.eventEmitter.addListener(FWEventName.VideoFeedClick, (event) => { FWLoggerUtil.log(`Receive VideoFeedClick event id: ${event?.info.id}`); if (this.onVideoFeedClick && event) { this.onVideoFeedClick(event); } }); if (Platform.OS === 'ios') { this.eventEmitter.addListener( FWEventName.NativeAppLanguageUpdated, (event) => { FWLoggerUtil.log( `Receive NativeAppLanguageUpdated event appLanguage: ${event.appLanguage}` ); const language = event.appLanguage; this.updateInternalAppLanguage(language); } ); } } /** * Mark the initialization method as being called on the JS side. * @param {MarkInitCalledOptions} options */ public async markInitCalled( options: MarkInitCalledOptions = { directlyCallNativeInit: false, } ): Promise { FWLoggerUtil.log('Call FireworkSDK markInitCalled method'); if (options.directlyCallNativeInit) { await this.completeRemainingInitialization(options); await FireworkSDKModule.markInitCalled(); } else { const initSucceeded = await FireworkSDKModule.markInitCalled(); FWLoggerUtil.log( `FireworkSDK markInitCalled initSucceeded: ${initSucceeded}` ); if (initSucceeded) { await this.completeRemainingInitialization(options); } } } /** * Open Video URL. * @param {string} url * @param {VideoPlayerConfiguration} config */ public async openVideoPlayer( url: string, config?: OpenVideoPlayerConfiguration ): Promise { if (!FWGlobalState.getInstance().sdkInitSucceeded) { await FWGlobalState.getInstance().sdkInitSucceededPromise; } FireworkSDKModule.openVideoPlayer(url, config ?? {}); } /** * Open Video Player with a source. * * Each source type carries exactly the parameters it needs: * ```typescript * sdk.openVideoPlayerWithSource( * { type: 'channel', channelId: 'OK1BXMy' }, * ); * sdk.openVideoPlayerWithSource( * { type: 'playlist', channelId: 'OK1BXMy', playlistId: '5maAAm' }, * { enablePictureInPicture: true }, * ); * ``` */ public async openVideoPlayerWithSource( source: OpenVideoPlayerSource, config?: OpenVideoPlayerConfiguration ): Promise { if (!FWGlobalState.getInstance().sdkInitSucceeded) { await FWGlobalState.getInstance().sdkInitSucceededPromise; } const nativeParams: Record = flattenSourceToNativeParams(source); if (config !== undefined) { nativeParams.config = config; } FireworkSDKModule.openVideoPlayerWithSource(nativeParams); } /** * Records the user purchase. * * Shopping V2 purchase tracking (parity with Web `analytics.purchase`) * requires `orderId`, `value` (final amount paid, sent as `order_value`), * `currencyCode`, `subtotal`, and a non-empty `products` list (sent as * `line_items`). * * Send all purchase events without client-side filtering; Firework * identifies attributed vs non-attributed purchases server-side. * @param {TrackPurchaseParameters} parameters */ public async trackPurchase( parameters: TrackPurchaseParameters ): Promise { const missingFields = TrackPurchaseParametersUtil.missingRequiredV2Fields(parameters); if (missingFields.length > 0) { FWLoggerUtil.log( `trackPurchase is missing required purchase fields: ${missingFields.join( ', ' )}` ); } if (!FWGlobalState.getInstance().sdkInitSucceeded) { await FWGlobalState.getInstance().sdkInitSucceededPromise; } FireworkSDKModule.trackPurchase(parameters); } /** * Change App level language. * @param {string | undefined | null} language Such as en, ar and en-US. If language is undefined or null or empty string, SDK will use system language. * @returns {Promise} The result of changing app language. */ public async changeAppLanguage(language?: string | null): Promise { if (!FWGlobalState.getInstance().sdkInitSucceeded) { await FWGlobalState.getInstance().sdkInitSucceededPromise; } const result = await FireworkSDKModule.changeAppLanguage(language); if (result) { const languageChanged = this._appLanguage !== language; if (Platform.OS === 'ios' && languageChanged) { await this.navigator.stopFloatingPlayer(); } this.updateInternalAppLanguage(language); } return result; } /** * Get the component type based on feedId. * @param {string} feedId * @returns {string | undefined} The component type based on feedId. */ public getComponentType(feedId: string) { return FWGlobalState.getInstance().componentTypeByFeedIdMap.get(feedId); } private updateInternalAppLanguage(language?: string | null) { const valueHasChanged = this._appLanguage !== language; this._appLanguage = language; if (valueHasChanged) { this.eventEmitter.emit(FWEventName.AppLanguageUpdated); } } private updateInternalShareBaseURL(shareBaseURL?: string) { const shareBaseURLChanged = this._shareBaseURL !== shareBaseURL; this._shareBaseURL = shareBaseURL; FireworkSDKModule.setShareBaseURL(shareBaseURL ?? '').then(() => { if (shareBaseURLChanged) { this.eventEmitter.emit(FWEventName.ShareBaseURLUpdated); } }); } private async completeRemainingInitialization( options: MarkInitCalledOptions = { directlyCallNativeInit: false, } ): Promise { if (FWGlobalState.getInstance().sdkInitSucceeded) { return; } FWGlobalState.getInstance().resolveSdkInitSucceeded(); if (!options.directlyCallNativeInit) { await ShoppingModule.init(); await LiveStreamModule.init(); } } } function flattenSourceToNativeParams( source: OpenVideoPlayerSource ): Record { switch (source.type) { case 'channel': return { source: source.type, channel: source.channelId }; case 'playlist': return { source: source.type, channel: source.channelId, playlist: source.playlistId, }; case 'dynamicContent': return { source: source.type, channel: source.channelId, dynamicContentParameters: source.parameters, }; case 'hashtagPlaylist': return { source: source.type, channel: source.channelId, hashtagFilterExpression: source.filterExpression, }; case 'sku': return { source: source.type, channel: source.channelId, productIds: source.productIds, }; case 'singleContent': return { source: source.type, contentId: source.contentId }; } } export default FireworkSDK;