@@ -36,6 +36,10 @@ export const getHomes = createAsyncThunk<Home[], void>('tibber/getHomes', async
3636 return result . viewer . homes
3737} )
3838
39+ interface ConsumptionArgs extends RangeOptions {
40+ homeId : string
41+ }
42+
3943interface ConsumptionResult {
4044 viewer : {
4145 home : {
@@ -46,18 +50,13 @@ interface ConsumptionResult {
4650 }
4751}
4852
49- export const getConsumption = createAsyncThunk <
50- ConsumptionNode [ ] ,
51- {
52- homeId : string
53- interval : Interval
54- last ?: number
55- }
56- > ( 'tibber/getConsumption' , async ( args ) => {
57- const result = await doRequest < ConsumptionResult > ( `{
53+ export const getConsumption = createAsyncThunk < ConsumptionNode [ ] , ConsumptionArgs > (
54+ 'tibber/getConsumption' ,
55+ async ( args ) => {
56+ const result = await doRequest < ConsumptionResult > ( `{
5857 viewer {
5958 home(id: "${ args . homeId } ") {
60- consumption(resolution: ${ args . interval } , last: ${ args . last || 100 } ) {
59+ consumption(${ rangeParameters ( args ) } ) {
6160 nodes {
6261 from
6362 to
@@ -69,11 +68,16 @@ export const getConsumption = createAsyncThunk<
6968 }
7069 }` )
7170
72- if ( ! result . viewer . home . consumption ) {
73- throw new Error ( 'missing consumption data' )
71+ if ( ! result . viewer . home . consumption ) {
72+ throw new Error ( 'missing consumption data' )
73+ }
74+ return result . viewer . home . consumption . nodes
7475 }
75- return result . viewer . home . consumption . nodes
76- } )
76+ )
77+
78+ interface PriceArgs extends RangeOptions {
79+ homeId : string
80+ }
7781
7882interface PriceResult {
7983 viewer : {
@@ -89,20 +93,15 @@ interface PriceResult {
8993 }
9094}
9195
92- export const getPrice = createAsyncThunk <
93- PriceNode [ ] ,
94- {
95- homeId : string
96- interval : Interval
97- last ?: number
98- }
99- > ( 'tibber/getPrice' , async ( args ) => {
100- const result = await doRequest < PriceResult > ( `{
96+ export const getPrice = createAsyncThunk < PriceNode [ ] , PriceArgs > (
97+ 'tibber/getPrice' ,
98+ async ( args ) => {
99+ const result = await doRequest < PriceResult > ( `{
101100 viewer {
102101 home(id: "${ args . homeId } ") {
103102 currentSubscription{
104103 priceInfo{
105- range(resolution: ${ args . interval } , last: ${ args . last || 100 } ){
104+ range(${ rangeParameters ( args ) } ){
106105 nodes{
107106 startsAt,
108107 total,
@@ -113,11 +112,12 @@ export const getPrice = createAsyncThunk<
113112 }
114113 }
115114 }` )
116- if ( ! result . viewer . home . currentSubscription . priceInfo . range ) {
117- throw new Error ( 'no price data found in range' )
115+ if ( ! result . viewer . home . currentSubscription . priceInfo . range ) {
116+ throw new Error ( 'no price data found in range' )
117+ }
118+ return result . viewer . home . currentSubscription . priceInfo . range . nodes
118119 }
119- return result . viewer . home . currentSubscription . priceInfo . range . nodes
120- } )
120+ )
121121
122122async function doRequest < T > ( query : string ) {
123123 const init : RequestInit = {
@@ -144,3 +144,50 @@ async function doRequest<T>(query: string) {
144144interface GQLResponse < T = any > {
145145 data : T
146146}
147+
148+ interface RangeOptions {
149+ resolution : Interval
150+
151+ after ?: Date
152+ before ?: Date
153+ first ?: number
154+ last ?: number
155+ }
156+
157+ function rangeParameters ( args : RangeOptions ) : string {
158+ if ( args . after && args . before ) throw new Error ( 'invalid combination: before && after' )
159+ if ( args . first && args . last ) throw new Error ( 'invalid combination: last && first' )
160+
161+ return Object . entries ( args )
162+ . filter ( ( [ name , value ] ) => {
163+ if ( value === undefined ) return false
164+ // Allowlist with the args we are using as options
165+ return [ 'resolution' , 'after' , 'before' , 'first' , 'last' ] . indexOf ( name ) !== - 1
166+ } )
167+ . map < [ string , string ] > ( ( [ name , value ] ) => {
168+ if ( value instanceof Date ) {
169+ // Before / after on a hourly resolution is > and <, not >= and <=.
170+ // Decrease the timestamp by a millisecond to include the first or last hour we are looking for.
171+ switch ( name ) {
172+ case 'after' :
173+ value = new Date ( value . getTime ( ) - 1 )
174+ break
175+ case 'before' :
176+ value = new Date ( value . getTime ( ) + 1 )
177+ break
178+ }
179+
180+ return [ name , `"${ btoa ( value . toISOString ( ) ) } "` ]
181+ } else if ( typeof value === 'number' ) {
182+ return [ name , value . toFixed ( 0 ) ]
183+ } else {
184+ value = '' + value
185+ }
186+
187+ return [ name , value ]
188+ } )
189+ . map ( ( [ name , value ] ) => {
190+ return `${ name } : ${ value } `
191+ } )
192+ . join ( ', ' )
193+ }
0 commit comments