@@ -11,6 +11,9 @@ import {
1111} from '/samples/phase-17/shared/overlayCycleInput.js' ;
1212
1313const overlayRuntimePreferenceMemoryStore = new Map ( ) ;
14+ const OVERLAY_RUNTIME_SHARE_PACKAGE_FORMAT = 'overlay-runtime-share-package' ;
15+ const OVERLAY_RUNTIME_SHARE_PACKAGE_VERSION = 1 ;
16+ const OVERLAY_RUNTIME_PROFILE_SCHEMA_VERSION = 1 ;
1417const OVERLAY_RUNTIME_DEFAULT_PRESET_DEFINITIONS = Object . freeze ( [
1518 Object . freeze ( {
1619 id : 'minimal' ,
@@ -539,6 +542,96 @@ function resolveOverlayRuntimePresetFromLibrary(library = [], presetOrId = '') {
539542 return normalizeOverlayRuntimePresetEntry ( presetOrId ) ;
540543}
541544
545+ function validateOverlayRuntimeSharePackagePayload ( payload , runtime = null ) {
546+ const errors = [ ] ;
547+ if ( ! payload || typeof payload !== 'object' || Array . isArray ( payload ) ) {
548+ return {
549+ valid : false ,
550+ errors : Object . freeze ( [ 'Overlay runtime share package must be an object.' ] ) ,
551+ value : null ,
552+ } ;
553+ }
554+
555+ const format = String ( payload . format || '' ) . trim ( ) ;
556+ if ( format !== OVERLAY_RUNTIME_SHARE_PACKAGE_FORMAT ) {
557+ errors . push ( `Overlay runtime share package format must be "${ OVERLAY_RUNTIME_SHARE_PACKAGE_FORMAT } ".` ) ;
558+ }
559+
560+ const packageVersion = Number ( payload . packageVersion ) ;
561+ if ( ! Number . isInteger ( packageVersion ) || packageVersion <= 0 ) {
562+ errors . push ( 'Overlay runtime share package version must be a positive integer.' ) ;
563+ } else if ( packageVersion > OVERLAY_RUNTIME_SHARE_PACKAGE_VERSION ) {
564+ errors . push ( `Overlay runtime share package version ${ packageVersion } is not supported by this runtime.` ) ;
565+ }
566+
567+ if ( payload . compatibility !== undefined ) {
568+ if ( ! payload . compatibility || typeof payload . compatibility !== 'object' || Array . isArray ( payload . compatibility ) ) {
569+ errors . push ( 'Overlay runtime share package compatibility must be an object when provided.' ) ;
570+ } else if ( payload . compatibility . profileSchemaVersion !== undefined ) {
571+ const profileSchemaVersion = Number ( payload . compatibility . profileSchemaVersion ) ;
572+ if ( ! Number . isInteger ( profileSchemaVersion ) || profileSchemaVersion <= 0 ) {
573+ errors . push ( 'Overlay runtime share package compatibility.profileSchemaVersion must be a positive integer when provided.' ) ;
574+ } else if ( profileSchemaVersion > OVERLAY_RUNTIME_PROFILE_SCHEMA_VERSION ) {
575+ errors . push (
576+ `Overlay runtime share package profile schema version ${ profileSchemaVersion } is not supported by this runtime.`
577+ ) ;
578+ }
579+ }
580+ }
581+
582+ const validatedProfile = validateOverlayRuntimePreferencePayload ( payload . profile ) ;
583+ if ( ! validatedProfile . valid || ! validatedProfile . value ) {
584+ for ( let i = 0 ; i < validatedProfile . errors . length ; i += 1 ) {
585+ errors . push ( `Share package profile: ${ validatedProfile . errors [ i ] } ` ) ;
586+ }
587+ }
588+
589+ let preset = null ;
590+ if ( Object . prototype . hasOwnProperty . call ( payload , 'preset' ) ) {
591+ const normalizedPreset = normalizeOverlayRuntimePresetEntry ( payload . preset , 0 ) ;
592+ if ( ! normalizedPreset ) {
593+ errors . push ( 'Overlay runtime share package preset is invalid.' ) ;
594+ } else {
595+ preset = normalizedPreset ;
596+ }
597+ }
598+
599+ const rawPresetId = String ( payload . presetId || '' ) . trim ( ) ;
600+ const presetId = rawPresetId || ( preset ? preset . id : '' ) ;
601+ if ( rawPresetId && preset && preset . id !== rawPresetId ) {
602+ errors . push ( 'Overlay runtime share package presetId does not match included preset id.' ) ;
603+ }
604+ if ( presetId && ! preset ) {
605+ const availablePreset = resolveOverlayRuntimePresetFromLibrary (
606+ getOverlayGameplayRuntimePresetLibrary ( runtime , { includeDefaults : true } ) ,
607+ presetId
608+ ) ;
609+ if ( ! availablePreset ) {
610+ errors . push ( `Overlay runtime share package requires preset "${ presetId } " which is not available in this runtime.` ) ;
611+ }
612+ }
613+
614+ if ( errors . length > 0 ) {
615+ return {
616+ valid : false ,
617+ errors : Object . freeze ( errors ) ,
618+ value : null ,
619+ } ;
620+ }
621+
622+ return {
623+ valid : true ,
624+ errors : Object . freeze ( [ ] ) ,
625+ value : Object . freeze ( {
626+ format,
627+ packageVersion,
628+ profile : validatedProfile . value ,
629+ presetId,
630+ preset,
631+ } ) ,
632+ } ;
633+ }
634+
542635function buildOverlayRuntimeLayoutPreferenceSnapshot ( runtime ) {
543636 const snapshot = { } ;
544637 const overrides = runtime ?. interactionLayoutOverrides ;
@@ -1802,14 +1895,53 @@ export function getOverlayGameplayRuntimePreferencesSnapshot(runtime) {
18021895export function exportOverlayGameplayRuntimeProfile ( runtime , { pretty = false } = { } ) {
18031896 const snapshot = getOverlayGameplayRuntimePreferencesSnapshot ( runtime ) ;
18041897 const payload = {
1805- version : 1 ,
1898+ version : OVERLAY_RUNTIME_PROFILE_SCHEMA_VERSION ,
18061899 visibility : snapshot . visibility ,
18071900 layout : snapshot . layout ,
18081901 keybindProfile : snapshot . keybindProfile ,
18091902 } ;
18101903 return JSON . stringify ( payload , null , pretty ? 2 : 0 ) ;
18111904}
18121905
1906+ export function exportOverlayGameplayRuntimeSharePackage ( runtime , options = { } ) {
1907+ const snapshot = getOverlayGameplayRuntimePreferencesSnapshot ( runtime ) ;
1908+ const profile = {
1909+ version : OVERLAY_RUNTIME_PROFILE_SCHEMA_VERSION ,
1910+ visibility : snapshot . visibility ,
1911+ layout : snapshot . layout ,
1912+ keybindProfile : snapshot . keybindProfile ,
1913+ } ;
1914+
1915+ const sharePackage = {
1916+ format : OVERLAY_RUNTIME_SHARE_PACKAGE_FORMAT ,
1917+ packageVersion : OVERLAY_RUNTIME_SHARE_PACKAGE_VERSION ,
1918+ compatibility : {
1919+ profileSchemaVersion : OVERLAY_RUNTIME_PROFILE_SCHEMA_VERSION ,
1920+ } ,
1921+ source : String ( options ?. source || 'overlay-gameplay-runtime' ) . trim ( ) || 'overlay-gameplay-runtime' ,
1922+ exportedAt : typeof options ?. exportedAt === 'string' && options . exportedAt . trim ( )
1923+ ? options . exportedAt . trim ( )
1924+ : new Date ( ) . toISOString ( ) ,
1925+ profile,
1926+ } ;
1927+
1928+ const hasPresetSelection = options ?. presetOrId !== undefined && options ?. presetOrId !== null && options ?. presetOrId !== '' ;
1929+ if ( hasPresetSelection ) {
1930+ const presetLibrary = getOverlayGameplayRuntimePresetLibrary ( runtime , {
1931+ includeDefaults : options ?. includeDefaults !== false ,
1932+ } ) ;
1933+ const resolvedPreset = resolveOverlayRuntimePresetFromLibrary ( presetLibrary , options . presetOrId ) ;
1934+ if ( resolvedPreset ) {
1935+ sharePackage . presetId = resolvedPreset . id ;
1936+ if ( options ?. includePreset !== false ) {
1937+ sharePackage . preset = resolvedPreset ;
1938+ }
1939+ }
1940+ }
1941+
1942+ return JSON . stringify ( sharePackage , null , options ?. pretty === true ? 2 : 0 ) ;
1943+ }
1944+
18131945export function saveOverlayGameplayRuntimePreferences ( runtime , options = { } ) {
18141946 if ( ! runtime ) {
18151947 return false ;
@@ -1894,6 +2026,70 @@ export function importOverlayGameplayRuntimeProfile(runtime, profileInput, optio
18942026 } ) ;
18952027}
18962028
2029+ export function importOverlayGameplayRuntimeSharePackage ( runtime , sharePackageInput , options = { } ) {
2030+ if ( ! runtime ) {
2031+ return Object . freeze ( {
2032+ success : false ,
2033+ errors : Object . freeze ( [ 'Overlay runtime is required for share package import.' ] ) ,
2034+ presetId : '' ,
2035+ presetRegistered : false ,
2036+ } ) ;
2037+ }
2038+
2039+ let parsedInput = null ;
2040+ if ( typeof sharePackageInput === 'string' ) {
2041+ try {
2042+ parsedInput = JSON . parse ( sharePackageInput ) ;
2043+ } catch {
2044+ return Object . freeze ( {
2045+ success : false ,
2046+ errors : Object . freeze ( [ 'Overlay runtime share package JSON is invalid.' ] ) ,
2047+ presetId : '' ,
2048+ presetRegistered : false ,
2049+ } ) ;
2050+ }
2051+ } else {
2052+ parsedInput = cloneJsonCompatibleValue ( sharePackageInput ) ;
2053+ }
2054+
2055+ const validated = validateOverlayRuntimeSharePackagePayload ( parsedInput , runtime ) ;
2056+ if ( ! validated . valid || ! validated . value ) {
2057+ return Object . freeze ( {
2058+ success : false ,
2059+ errors : validated . errors ,
2060+ presetId : '' ,
2061+ presetRegistered : false ,
2062+ } ) ;
2063+ }
2064+
2065+ let presetRegistered = false ;
2066+ if ( validated . value . preset && options ?. registerPreset !== false ) {
2067+ const customLibrary = getOverlayGameplayRuntimePresetLibrary ( runtime , { includeDefaults : false } ) ;
2068+ setOverlayGameplayRuntimePresetLibrary ( runtime , [ ...customLibrary , validated . value . preset ] ) ;
2069+ presetRegistered = true ;
2070+ }
2071+
2072+ const profilePayload = createOverlayRuntimePreferencePayloadFromValidated ( validated . value . profile ) ;
2073+ const importResult = importOverlayGameplayRuntimeProfile ( runtime , profilePayload , {
2074+ persist : options ?. persist !== false ,
2075+ } ) ;
2076+ if ( importResult . success !== true ) {
2077+ return Object . freeze ( {
2078+ success : false ,
2079+ errors : importResult . errors ,
2080+ presetId : validated . value . presetId ,
2081+ presetRegistered,
2082+ } ) ;
2083+ }
2084+
2085+ return Object . freeze ( {
2086+ success : true ,
2087+ errors : Object . freeze ( [ ] ) ,
2088+ presetId : validated . value . presetId ,
2089+ presetRegistered,
2090+ } ) ;
2091+ }
2092+
18972093export function setOverlayGameplayRuntimeContextInputMap ( runtime , contextInputMap ) {
18982094 if ( ! runtime ) {
18992095 return false ;
0 commit comments