@@ -16,12 +16,15 @@ limitations under the License.
16
16
*/
17
17
18
18
import { Optional } from "matrix-events-sdk" ;
19
+ import { mergeWith } from "lodash" ;
19
20
20
21
import { SnakedObject } from "./utils/SnakedObject" ;
21
22
import { IConfigOptions , ISsoRedirectOptions } from "./IConfigOptions" ;
23
+ import { isObject , objectClone } from "./utils/objects" ;
24
+ import { DeepReadonly , Defaultize } from "./@types/common" ;
22
25
23
26
// see element-web config.md for docs, or the IConfigOptions interface for dev docs
24
- export const DEFAULTS : IConfigOptions = {
27
+ export const DEFAULTS : DeepReadonly < IConfigOptions > = {
25
28
brand : "Element" ,
26
29
integrations_ui_url : "https://scalar.vector.im/" ,
27
30
integrations_rest_url : "https://scalar.vector.im/api" ,
@@ -50,13 +53,43 @@ export const DEFAULTS: IConfigOptions = {
50
53
chunk_length : 2 * 60 , // two minutes
51
54
max_length : 4 * 60 * 60 , // four hours
52
55
} ,
56
+
57
+ feedback : {
58
+ existing_issues_url :
59
+ "https://github.com/vector-im/element-web/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc" ,
60
+ new_issue_url : "https://github.com/vector-im/element-web/issues/new/choose" ,
61
+ } ,
53
62
} ;
54
63
64
+ export type ConfigOptions = Defaultize < IConfigOptions , typeof DEFAULTS > ;
65
+
66
+ function mergeConfig (
67
+ config : DeepReadonly < IConfigOptions > ,
68
+ changes : DeepReadonly < Partial < IConfigOptions > > ,
69
+ ) : DeepReadonly < IConfigOptions > {
70
+ // return { ...config, ...changes };
71
+ return mergeWith ( objectClone ( config ) , changes , ( objValue , srcValue ) => {
72
+ // Don't merge arrays, prefer values from newer object
73
+ if ( Array . isArray ( objValue ) ) {
74
+ return srcValue ;
75
+ }
76
+
77
+ // Don't allow objects to get nulled out, this will break our types
78
+ if ( isObject ( objValue ) && ! isObject ( srcValue ) ) {
79
+ return objValue ;
80
+ }
81
+ } ) ;
82
+ }
83
+
84
+ type ObjectType < K extends keyof IConfigOptions > = IConfigOptions [ K ] extends object
85
+ ? SnakedObject < NonNullable < IConfigOptions [ K ] > >
86
+ : Optional < SnakedObject < NonNullable < IConfigOptions [ K ] > > > ;
87
+
55
88
export default class SdkConfig {
56
- private static instance : IConfigOptions ;
57
- private static fallback : SnakedObject < IConfigOptions > ;
89
+ private static instance : DeepReadonly < IConfigOptions > ;
90
+ private static fallback : SnakedObject < DeepReadonly < IConfigOptions > > ;
58
91
59
- private static setInstance ( i : IConfigOptions ) : void {
92
+ private static setInstance ( i : DeepReadonly < IConfigOptions > ) : void {
60
93
SdkConfig . instance = i ;
61
94
SdkConfig . fallback = new SnakedObject ( i ) ;
62
95
@@ -69,40 +102,37 @@ export default class SdkConfig {
69
102
public static get < K extends keyof IConfigOptions = never > (
70
103
key ?: K ,
71
104
altCaseName ?: string ,
72
- ) : IConfigOptions | IConfigOptions [ K ] {
105
+ ) : DeepReadonly < IConfigOptions > | DeepReadonly < IConfigOptions > [ K ] {
73
106
if ( key === undefined ) {
74
107
// safe to cast as a fallback - we want to break the runtime contract in this case
75
108
return SdkConfig . instance || < IConfigOptions > { } ;
76
109
}
77
110
return SdkConfig . fallback . get ( key , altCaseName ) ;
78
111
}
79
112
80
- public static getObject < K extends keyof IConfigOptions > (
81
- key : K ,
82
- altCaseName ?: string ,
83
- ) : Optional < SnakedObject < NonNullable < IConfigOptions [ K ] > > > {
113
+ public static getObject < K extends keyof IConfigOptions > ( key : K , altCaseName ?: string ) : ObjectType < K > {
84
114
const val = SdkConfig . get ( key , altCaseName ) ;
85
- if ( val !== null && val !== undefined ) {
115
+ if ( isObject ( val ) ) {
86
116
return new SnakedObject ( val ) ;
87
117
}
88
118
89
119
// return the same type for sensitive callers (some want `undefined` specifically)
90
- return val === undefined ? undefined : null ;
120
+ return ( val === undefined ? undefined : null ) as ObjectType < K > ;
91
121
}
92
122
93
- public static put ( cfg : Partial < IConfigOptions > ) : void {
94
- SdkConfig . setInstance ( { ... DEFAULTS , ... cfg } ) ;
123
+ public static put ( cfg : DeepReadonly < ConfigOptions > ) : void {
124
+ SdkConfig . setInstance ( mergeConfig ( DEFAULTS , cfg ) ) ;
95
125
}
96
126
97
127
/**
98
- * Resets the config to be completely empty .
128
+ * Resets the config.
99
129
*/
100
- public static unset ( ) : void {
101
- SdkConfig . setInstance ( < IConfigOptions > { } ) ; // safe to cast - defaults will be applied
130
+ public static reset ( ) : void {
131
+ SdkConfig . setInstance ( mergeConfig ( DEFAULTS , { } ) ) ; // safe to cast - defaults will be applied
102
132
}
103
133
104
- public static add ( cfg : Partial < IConfigOptions > ) : void {
105
- SdkConfig . put ( { ... SdkConfig . get ( ) , ... cfg } ) ;
134
+ public static add ( cfg : Partial < ConfigOptions > ) : void {
135
+ SdkConfig . put ( mergeConfig ( SdkConfig . get ( ) , cfg ) ) ;
106
136
}
107
137
}
108
138
0 commit comments