├── .devcontainer └── devcontainer.json ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── node.js.yml │ └── npm-publish.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── README.md ├── Version.ts ├── _config.yml ├── examples ├── get-consumption.ts ├── get-energy-prices.ts ├── get-homes-json.ts ├── get-homes.ts ├── push-notification.ts ├── tibber-feed-full.ts └── tibber-feed-simple.ts ├── jestconfig.json ├── jsdoc ├── TibberFeed.html ├── TibberQuery.html ├── TibberQueryBase.html ├── fonts │ ├── OpenSans-Bold-webfont.eot │ ├── OpenSans-Bold-webfont.svg │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-BoldItalic-webfont.eot │ ├── OpenSans-BoldItalic-webfont.svg │ ├── OpenSans-BoldItalic-webfont.woff │ ├── OpenSans-Italic-webfont.eot │ ├── OpenSans-Italic-webfont.svg │ ├── OpenSans-Italic-webfont.woff │ ├── OpenSans-Light-webfont.eot │ ├── OpenSans-Light-webfont.svg │ ├── OpenSans-Light-webfont.woff │ ├── OpenSans-LightItalic-webfont.eot │ ├── OpenSans-LightItalic-webfont.svg │ ├── OpenSans-LightItalic-webfont.woff │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.svg │ └── OpenSans-Regular-webfont.woff ├── index.html ├── models_enums_PriceLevel.js.html ├── models_enums_PriceRatingLevel.js.html ├── nodes_TibberFeed.js.html ├── nodes_TibberQuery.js.html ├── nodes_TibberQueryBase.js.html ├── scripts │ ├── linenumber.js │ └── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js └── styles │ ├── jsdoc-default.css │ ├── prettify-jsdoc.css │ └── prettify-tomorrow.css ├── package-lock.json ├── package.json ├── src ├── gql │ ├── consumption.gql.ts │ ├── energy.gql.ts │ ├── home.gql.ts │ ├── homes.gql.ts │ ├── sendPushNotification.gql.ts │ └── websocketSubscriptionUrl.ts ├── index.ts ├── models │ ├── IAddress.ts │ ├── IConfig.ts │ ├── IConsumption.ts │ ├── IContactInfo.ts │ ├── ICurrentSubscription.ts │ ├── IEndpoint.ts │ ├── IErrors.ts │ ├── IHome.ts │ ├── IHomeConsumptionConnection.ts │ ├── IHomeConsumptionEdge.ts │ ├── IHomeConsumptionPageInfo.ts │ ├── IHomeFeatures.ts │ ├── IHomeProductionConnection.ts │ ├── IHomeProductionEdge.ts │ ├── IHomeProductionPageInfo.ts │ ├── ILegalEntity.ts │ ├── ILiveMeasurement.ts │ ├── IMeteringPointData.ts │ ├── IPrice.ts │ ├── IPriceInfo.ts │ ├── IPriceRating.ts │ ├── IProduction.ts │ ├── IQuery.ts │ ├── IQueryPayload.ts │ ├── ISendPushNotification.ts │ ├── ISendPushNotificationPayload.ts │ ├── ISubscription.ts │ ├── ISubscriptionPriceConnection.ts │ ├── ISubscriptionPriceConnectionPageInfo.ts │ ├── ISubscriptionPriceEdge.ts │ ├── PriceRatingEntry.ts │ ├── PriceRatingThresholdPercentages.ts │ ├── PriceRatingType.ts │ └── enums │ │ ├── AppScreen.ts │ │ ├── EnergyResolution.ts │ │ ├── HeatingSource.ts │ │ ├── HomeAvatar.ts │ │ ├── HomeType.ts │ │ ├── PriceLevel.ts │ │ ├── PriceRatingLevel.ts │ │ └── PriceResolution.ts ├── nodes │ ├── TibberFeed.ts │ ├── TibberQuery.ts │ ├── TibberQueryBase.ts │ └── models │ │ ├── GQL.ts │ │ ├── HttpMethod.ts │ │ ├── TimeoutError.ts │ │ └── tools.ts └── tools │ └── HeaderManager.ts ├── test-sequencer.js ├── tests ├── test0-header-manager-tests │ ├── test1-sanitize-user-agent.ts │ ├── test2-valid-user-agent.ts │ ├── test3-sanitized-user-agent.ts │ ├── test4-truncate-user-agent.ts │ └── test5-user-agent-set-only-once.ts ├── test1-integration-tibber-query.ts ├── test2-integration-tibber-feed.ts ├── test3-tibber-api-endpoint.ts ├── test4-tibber-feed-1.ts ├── test4-tibber-feed-2.ts └── test5-tibber-query.ts ├── tsconfig.json └── tslint.json /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Node.js & TypeScript", 3 | "image": "mcr.microsoft.com/devcontainers/typescript-node:20-bookworm", 4 | "features": { 5 | "ghcr.io/devcontainers/features/docker-in-docker:1": {}, 6 | "ghcr.io/devcontainers/features/git:1": {}, 7 | "ghcr.io/devcontainers/features/git-lfs:1": {}, 8 | "ghcr.io/devcontainers/features/github-cli:1": {}, 9 | "ghcr.io/stuartleeks/dev-container-features/shell-history:0": {} 10 | }, 11 | // Features to add to the dev container. More info: https://containers.dev/implementors/features. 12 | // "features": {}, 13 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 14 | // "forwardPorts": [], 15 | // Use 'postCreateCommand' to run commands after the container is created. 16 | // "postCreateCommand": "yarn install", 17 | // Configure tool-specific properties. 18 | "customizations": { 19 | "vscode": { 20 | "extensions": [ 21 | "github.vscode-pull-request-github", 22 | "ms-vscode.vs-keybindings", 23 | "dbaeumer.vscode-eslint", 24 | "esbenp.prettier-vscode", 25 | "Orta.vscode-jest", 26 | "Gruntfuggly.todo-tree", 27 | "github.vscode-github-actions" 28 | ] 29 | } 30 | }, 31 | "mounts": [ 32 | "type=bind,source=/home/${localEnv:USER}/.ssh,target=/home/node/.ssh", 33 | "type=bind,source=/home/${localEnv:USER}/.zshrc,target=/home/node/.zshrc" 34 | ], 35 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 36 | // "remoteUser": "root" 37 | } -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '45 7 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'typescript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v3 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v3 71 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ "**" ] 9 | pull_request: 10 | branches: [ "**" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [lts/*] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | fail-fast: true 22 | max-parallel: 1 23 | 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Use Node.js ${{ matrix.node-version }} 28 | uses: actions/setup-node@v4 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | cache: 'npm' 32 | - run: npm ci 33 | - run: npm run build --if-present 34 | - run: npm test 35 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: lts/* 18 | - run: npm ci 19 | - run: npm run build 20 | - run: npm test 21 | 22 | publish-npm: 23 | needs: build 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: actions/setup-node@v4 28 | with: 29 | node-version: lts/* 30 | registry-url: https://registry.npmjs.org/ 31 | - run: npm ci 32 | - run: npm run build 33 | - if: ${{ github.event.release.prerelease }} 34 | run: npm publish . --tag beta 35 | env: 36 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 37 | - if: ${{ !github.event.release.prerelease }} 38 | run: npm publish . 39 | env: 40 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # IDE files 64 | .idea/ 65 | 66 | # Others 67 | lib/ 68 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .eslintrc.json 2 | .travis.yml 3 | .vscode/ 4 | _config.yml 5 | npm-release* 6 | src 7 | tsconfig.json 8 | tslint.json 9 | .prettierrc 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 150, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "tabWidth": 4 6 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Run Current File", 11 | "program": "${file}", 12 | "preLaunchTask": "tsc: build - tsconfig.json", 13 | "outFiles": [ 14 | "${workspaceFolder}/lib/**/*.js" 15 | ] 16 | }, 17 | { 18 | "type": "node", 19 | "request": "launch", 20 | "name": "Run Feed", 21 | "program": "${workspaceFolder}/examples/tibber-feed-simple.ts", 22 | "preLaunchTask": "tsc: build - tsconfig.json", 23 | "outFiles": [ 24 | "${workspaceFolder}/lib/**/*.js" 25 | ] 26 | }, 27 | { 28 | "type": "node", 29 | "request": "launch", 30 | "name": "Jest All", 31 | "program": "${workspaceFolder}/node_modules/.bin/jest", 32 | "args": [ 33 | "--runInBand", 34 | "--config", 35 | "${workspaceFolder}/jestconfig.json" 36 | ], 37 | "console": "integratedTerminal", 38 | "internalConsoleOptions": "neverOpen", 39 | "disableOptimisticBPs": true, 40 | "windows": { 41 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 42 | } 43 | }, 44 | { 45 | "type": "node", 46 | "request": "launch", 47 | "name": "Jest Current File", 48 | "program": "${workspaceFolder}/node_modules/.bin/jest", 49 | "args": [ 50 | "${fileBasenameNoExtension}", 51 | "--runInBand", 52 | "--detectOpenHandles", 53 | "--config", 54 | "${workspaceFolder}/jestconfig.json" 55 | ], 56 | "console": "integratedTerminal", 57 | "internalConsoleOptions": "neverOpen", 58 | "disableOptimisticBPs": true, 59 | "windows": { 60 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 61 | } 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "build", 9 | "problemMatcher": [ 10 | "$tsc" 11 | ], 12 | "group": "build" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2024 André Biseth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **tibber-api** 2 | 3 | Node.js module for querying data and integrating with Tibber Pulse through Tibber API. 4 | 5 | | Branch | Status | 6 | | ------ | ------ | 7 | | master | [![Node.js CI](https://github.com/bisand/tibber-api/actions/workflows/node.js.yml/badge.svg?branch=master)](https://github.com/bisand/tibber-api/actions/workflows/node.js.yml) [![Node.js Package](https://github.com/bisand/tibber-api/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/bisand/tibber-api/actions/workflows/npm-publish.yml) [![CodeQL](https://github.com/bisand/tibber-api/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/bisand/tibber-api/actions/workflows/codeql-analysis.yml) [![DeepScan grade](https://deepscan.io/api/teams/16513/projects/19829/branches/520472/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=16513&pid=19829&bid=520472) [![npm version](https://badge.fury.io/js/tibber-api.svg)](https://badge.fury.io/js/tibber-api) | 8 | 9 | ## General 10 | 11 | This Node.js module is used for communication with [Tibber API](https://developer.tibber.com/) through [GraphQL](https://developer.tibber.com/docs/overview) queries and for retrieving data from Tibber Pulse via websocket. 12 | [Tibber](https://tibber.com) is a norwegian technology focused power company which is providing tools to get more insight and controll over your home and its power consumption. 13 | 14 | > **Note** 15 | > 16 | > **`Breaking changes!`** 17 | > 18 | > Version 5 introduces breaking changes in **TibberFeed**. Due to the fact that [Tibber also has introduced breaking changes in their API](https://developer.tibber.com/docs/overview#breaking-websocket-change) we had to do some small changes. 19 | > 20 | >Instead of changing the whole implementation, the only noticable change is done to the [constructor](#TibberFeed) of `TibberFeed` which takes a `TibberQuery` instead of and `IConfig` object and uses the config from the provided `TibberQuery`. See example below. 21 | ```typescript 22 | ... 23 | // Instantiate TibberFeed. 24 | const tibberQuery = new TibberQuery(config); 25 | const tibberFeed = new TibberFeed(tibberQuery, 5000); 26 | ... 27 | ``` 28 | 29 | ## Prerequisites 30 | 31 | Click the link below to sign up, and receive 400 NOK to shop smart home gadgets from [Tibber Store](https://tibber.com/no/store): 32 | 33 | > **[https://invite.tibber.com/fjafad7b](https://invite.tibber.com/fjafad7b)** 34 | 35 | You will also need an API token from Tibber. Get it here: 36 | 37 | > https://developer.tibber.com/ 38 | 39 | ## Installation 40 | 41 | ### NPM package 42 | 43 | > https://www.npmjs.com/package/tibber-api 44 | 45 | ### Command line 46 | 47 | ```bash 48 | npm install tibber-api 49 | ``` 50 | 51 | ## TibberFeed 52 | 53 | Realtime power consuption data from Tibber Pulse. Provide API token, Home ID and select what kind of information you want to retrieve. 54 | 55 | > Note! There should be only one instance running of _TibberFeed_ per API key. Doing otherwise may return unpredictable result, or even error responses from the API. 56 | 57 | [JSDoc documentation](https://htmlpreview.github.io/?https://raw.githubusercontent.com/bisand/tibber-api/master/jsdoc/TibberFeed.html) 58 | 59 | ### Constructor 60 | 61 | ```typescript 62 | /** 63 | * Constructor for creating a new instance if TibberFeed. 64 | * @constructor 65 | * @param {TibberQueryBase} tibberQuery TibberQueryBase object. 66 | * @param {number} timeout Feed idle timeout in milliseconds. The feed will reconnect after being idle for more than the specified number of milliseconds. Min 5000 ms. 67 | * @param {boolean} returnAllFields Specify if you want to return all fields from the data feed. 68 | * @param {number} connectionTimeout Feed connection timeout. 69 | * @see {@linkcode TibberQueryBase} 70 | */ 71 | TibberFeed(tibberQuery: TibberQueryBase, timeout: number = 60000, returnAllFields = false, connectionTimeout: number = 30000) 72 | ``` 73 | 74 | > Created a new instance of TibberFeed with with an instance of [TibberQuery](#TibberQuery) and timeout. The timeout is used for reconnection when no data is received within the specified time. The [config object](#config-object) is described later in this document. 75 | 76 | ## Methods 77 | 78 | ### Connect 79 | 80 | ```typescript 81 | /** 82 | * Connect to Tibber feed. 83 | */ 84 | Connect(); 85 | ``` 86 | 87 | > Connect to Tibber Pulse realtime data feed. The connect method has a backoff logic with jitter applied to prevent too many reconnection attemts. There is also an idle timeout functionality that ensures that the feed is always connected when it's active. 88 | 89 | ### Close 90 | 91 | ```typescript 92 | /** 93 | * Close the Tibber feed. 94 | */ 95 | Close(); 96 | ``` 97 | 98 | > Disconnect from Tibber Pulse data feed. 99 | 100 | ## Events 101 | 102 | ### connected 103 | 104 | ```typescript 105 | /** 106 | * Event: open 107 | * Called when websocket connection is established. 108 | * @message Information message. 109 | */ 110 | connected(message: string) 111 | ``` 112 | 113 | > Called when the feed is connected to Tibber. 114 | 115 | ### connection_ack 116 | 117 | ```typescript 118 | /** 119 | * Event: connection_ack 120 | * Called when websocket connection is authenticated. 121 | * @message Information message. 122 | */ 123 | connection_ack(message: string) 124 | ``` 125 | 126 | > Called when the feed is authenticated. 127 | 128 | ### disconnected 129 | 130 | ```typescript 131 | /** 132 | * Event: disconnected 133 | * Called when websocket connection is disconnected. 134 | * @message Information message. 135 | */ 136 | disconnected(message: string) 137 | ``` 138 | 139 | > Called when the feed is disconnected from Tibber. 140 | 141 | ### data 142 | 143 | ```typescript 144 | /** 145 | * Event: data 146 | * Called when data is received through the websocket connection. 147 | * @data Incoming data message from Tibber API {@link https://developer.tibber.com/docs/reference}. 148 | * @see any 149 | */ 150 | data(data: any) 151 | ``` 152 | 153 | > Called when new data is available. 154 | 155 | ### error 156 | 157 | ```typescript 158 | /** 159 | * Log function to emit error log data to subscribers. 160 | * @param message Log message 161 | */ 162 | error(error: any) 163 | ``` 164 | 165 | > Called when the feed is logging errors. 166 | 167 | ### warn 168 | 169 | ```typescript 170 | /** 171 | * Log function to emit warning log data to subscribers. 172 | * @param message Log message 173 | */ 174 | warn(message: string) 175 | ``` 176 | 177 | > Called when the feed is logging warnings. 178 | 179 | ### log 180 | 181 | ```typescript 182 | /** 183 | * Log function to emit log data to subscribers. 184 | * @param message Log message 185 | */ 186 | log(message: string) 187 | ``` 188 | 189 | > Called when the feed is logging. 190 | 191 | ## TibberQuery 192 | 193 | Do basic calls to Tibber API using GraphQL queries. To query the Tibber API, simply provide raw GraphQL queries in the payload of the incoming message. See Tibber API documentation and API Explorer for more informations. 194 | 195 | [JSDoc documentation](https://htmlpreview.github.io/?https://raw.githubusercontent.com/bisand/tibber-api/master/jsdoc/TibberQuery.html) 196 | 197 | 198 | ### Constructor 199 | 200 | ```typescript 201 | /** 202 | * Constructor 203 | * Create an instace of TibberQuery class 204 | * @param {IConfig} config Config object 205 | * @param {number} requestTimeout Request timeout in milliseconds. 206 | * @see IConfig 207 | */ 208 | TibberQuery(config: IConfig, requestTimeout: number = 30000); 209 | ``` 210 | 211 | > Created a new instance of TibberQuery with the desired configuration. The config object is described later in this document. 212 | 213 | ## Methods 214 | 215 | ### query 216 | 217 | ```typescript 218 | /** 219 | * General GQL query 220 | * @param query GQL query. 221 | * @param variables Variables used by query parameter. 222 | * @return Query result as JSON data 223 | */ 224 | query(query: string, variables?: object): Promise 225 | ``` 226 | 227 | > Query Tibber API with GraphQL to retrieve data. See [Tibber documentation](https://developer.tibber.com/docs/overview) for more information on QraphQL. 228 | 229 | ### getHome 230 | 231 | ```typescript 232 | /** 233 | * Get selected home with some selected properties, including address and owner. 234 | * @param homeId Tibber home ID 235 | * @return IHome object 236 | */ 237 | getHome(homeId: string): Promise 238 | ``` 239 | 240 | > Get home registered to your Tibber account from home ID. This function will return a IHome object including general information. To retrieve complete IHome object, please use the getHomeComplete(homeId: string) function. 241 | 242 | ### getHomeComplete 243 | 244 | ```typescript 245 | /** 246 | * Get homes with all properties, including energy price, consumption and production. 247 | * @param homeId Tibber home ID 248 | * @return IHome object 249 | */ 250 | getHomeComplete(homeId: string): Promise 251 | ``` 252 | 253 | > Get home registered to your Tibber account from home ID. This function will return a home object including all information. 254 | 255 | ### getHomes 256 | 257 | ```typescript 258 | /** 259 | * Get homes with some selected properties, including address and owner. 260 | * @return Array of IHome. 261 | */ 262 | getHomes(): Promise 263 | ``` 264 | 265 | > Get all homes registered to your Tibber account. This function will return a list of homes including general information. To retrieve complete Home objects, please use the getHomesComplete() function. 266 | 267 | ### getHomesComplete 268 | 269 | ```typescript 270 | /** 271 | * Get homes with all properties, including energy price, consumption and production. 272 | * @return Array of IHome 273 | */ 274 | getHomesComplete(): Promise 275 | ``` 276 | 277 | > Get all homes registered to your Tibber account. This function will return a list of homes including all information. 278 | 279 | ### getCurrentEnergyPrice 280 | 281 | ```typescript 282 | /** 283 | * Get current energy price for selected home. 284 | * @param homeId Tibber home ID 285 | * @return IPrice object 286 | */ 287 | getCurrentEnergyPrice(homeId: string): Promise 288 | ``` 289 | 290 | > Get the current energy price for selected home. 291 | 292 | ### getCurrentEnergyPrices 293 | 294 | ```typescript 295 | /** 296 | * Get current energy prices from all homes registered to current user 297 | * @return Array of IPrice 298 | */ 299 | getCurrentEnergyPrices(): Promise 300 | ``` 301 | 302 | > Get current energy prices for all your homes. 303 | 304 | ### getTodaysEnergyPrices 305 | 306 | ```typescript 307 | /** 308 | * Get energy prices for today. 309 | * @param homeId Tibber home ID 310 | * @return Array of IPrice 311 | */ 312 | getTodaysEnergyPrices(homeId: string): Promise 313 | ``` 314 | 315 | > Get NorPool energy prices for today for selected home. 316 | 317 | ### getTomorrowsEnergyPrices 318 | 319 | ```typescript 320 | /** 321 | * Get energy prices for tomorrow. These will only be available between 12:00 and 23:59 322 | * @param homeId Tibber home ID 323 | * @return Array of IPrice 324 | */ 325 | getTomorrowsEnergyPrices(homeId: string): Promise 326 | ``` 327 | 328 | > Get NorPool energy prices for tomorrow for selected home. This will only return data between 12:00 and 23:59 329 | 330 | ### getConsumption 331 | 332 | ```typescript 333 | /** 334 | * Get energy consumption for one or more homes. 335 | * Returns an array of IConsumption 336 | * @param resolution EnergyResolution. Valid values: HOURLY, DAILY, WEEKLY, MONTHLY, ANNUAL 337 | * @param lastCount Return the last number of records 338 | * @param homeId Tibber home ID. Optional parameter. Empty parameter will return all registered homes. 339 | * @return Array of IConsumption 340 | */ 341 | getConsumption(resolution: EnergyResolution, lastCount: number, homeId?: string): Promise 342 | ``` 343 | 344 | > Get consumption for selected home, or all homes if homeId is not provided. EnergyResolution describes interval for data and lastCount specifies number of records to retrieve, returning the last number of records in the dataset. 345 | 346 | ### sendPushNotification 347 | 348 | ```typescript 349 | /** 350 | * Sends a push notification to the current user's tibber app. 351 | * Returns a ISendPushNotification Object 352 | * @param title: "The title of your message"; 353 | * @param message: "The message you want to send"; 354 | * @param screenToOpen: AppScreen Object, example: AppScreen.HOME ; 355 | * @return ISendPushNotification Object 356 | */ 357 | ``` 358 | 359 | > Send a notification to the tibber app. You can specify which route shoule be opened in the App when opening the message. The notification will be send to all devices registered for that tibber account. If the send is successful the response will show how many devices the message reached. 360 | 361 | ## Examples 362 | 363 | TibberFeed: typescript example. 364 | 365 | ```typescript 366 | import { TibberFeed, TibberQuery, TibberQueryBase, IConfig } from 'tibber-api'; 367 | 368 | // Config object needed when instantiating TibberQuery 369 | const config: IConfig = { 370 | // Endpoint configuration. 371 | apiEndpoint: { 372 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 373 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 374 | requestTimeout: 5000, 375 | }, 376 | // Query configuration. 377 | homeId: '96a14971-525a-4420-aae9-e5aedaa129ff', 378 | timestamp: true, 379 | power: true, 380 | }; 381 | 382 | // Instantiate TibberFeed. 383 | const tibberQuery = new TibberQuery(config); 384 | const tibberFeed = new TibberFeed(tibberQuery, 5000); 385 | 386 | // Subscribe to "data" event. 387 | tibberFeed.on('data', (data) => { 388 | console.log(data); 389 | }); 390 | 391 | // Connect to Tibber data feed 392 | tibberFeed.connect(); 393 | ``` 394 | 395 | TibberQuery: typescript example. 396 | 397 | ```typescript 398 | import { TibberQuery, IConfig } from 'tibber-api'; 399 | import http from 'http'; 400 | 401 | const hostname = '127.0.0.1'; 402 | const port = 3000; 403 | 404 | // Config object needed when instantiating TibberQuery 405 | const config: IConfig = { 406 | apiEndpoint: { 407 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 408 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 409 | requestTimeout: 5000, 410 | }, 411 | }; 412 | 413 | // GraphQL query 414 | const queryHomes = '{viewer{homes{id size appNickname appAvatar address{address1 address2 address3 postalCode city country latitude longitude}}}}'; 415 | 416 | // Instance of TibberQuery 417 | const tibberQuery = new TibberQuery(config); 418 | 419 | // Simple web server. 420 | const server = http.createServer(async (req, res) => { 421 | // Call the Tibber API and return the result. 422 | const result = await tibberQuery.query(queryHomes); 423 | res.statusCode = 200; 424 | res.setHeader('Content-Type', 'application/json'); 425 | res.end(JSON.stringify(result)); 426 | }); 427 | 428 | // Start web server. 429 | server.listen(port, hostname, () => { 430 | console.log(`Server running at http://${hostname}:${port}/`); 431 | }); 432 | ``` 433 | 434 | ## Config object 435 | 436 | IConfig: typescript example. 437 | 438 | ```typescript 439 | // Config object needed when instantiating TibberQuery 440 | const config: IConfig = { 441 | // Endpoint configuration. 442 | apiEndpoint: { 443 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 444 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 445 | requestTimeout: 5000, 446 | }, 447 | // Query configuration. 448 | homeId: '96a14971-525a-4420-aae9-e5aedaa129ff', 449 | timestamp: true, 450 | power: true, 451 | lastMeterConsumption: true, 452 | accumulatedConsumption: true, 453 | accumulatedProduction: true, 454 | accumulatedCost: true, 455 | accumulatedReward: true, 456 | currency: true, 457 | minPower: true, 458 | averagePower: true, 459 | maxPower: true, 460 | powerProduction: true, 461 | minPowerProduction: true, 462 | maxPowerProduction: true, 463 | lastMeterProduction: true, 464 | powerFactor: true, 465 | voltagePhase1: true, 466 | voltagePhase2: true, 467 | voltagePhase3: true, 468 | currentL1: true, 469 | currentL2: true, 470 | currentL3: true, 471 | signalStrength: true, 472 | }; 473 | ``` 474 | 475 | ## License 476 | 477 | [MIT]( 478 | //choosealicense.com/licenses/mit/) 479 | -------------------------------------------------------------------------------- /Version.ts: -------------------------------------------------------------------------------- 1 | export const version = "5.4.2"; 2 | export const versionMajor = 5; 3 | export const versionMinor = 4; 4 | export const versionPatch = 2; 5 | export const versionPrerelease = []; 6 | export const release = "5.4.2+20250519131329"; 7 | export const gitSha = "e1124260cbcc722d40d60ce3d3b1b09cf2726318"; 8 | export const gitDate = new Date(1747660409000); 9 | export default {version,versionMajor,versionMinor,versionPatch,versionPrerelease,release,gitSha,gitDate}; 10 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /examples/get-consumption.ts: -------------------------------------------------------------------------------- 1 | // Uncomment the following line to include tibber-api NPM package instead. 2 | // const TibberQuery = require("tibber-api").TibberQuery; 3 | 4 | import { TibberQuery, IConfig } from '../src/index'; 5 | import http from 'http'; 6 | import { EnergyResolution } from '../src/models/enums/EnergyResolution'; 7 | 8 | const hostname = '127.0.0.1'; 9 | const port = 3000; 10 | 11 | // Config object needed when instantiating TibberQuery 12 | const config: IConfig = { 13 | active: true, 14 | apiEndpoint: { 15 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 16 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 17 | }, 18 | }; 19 | 20 | // Instance of TibberQuery 21 | const tibberQuery = new TibberQuery(config); 22 | 23 | // Simple web server. 24 | const server = http.createServer(async (req, res) => { 25 | // Call the Tibber API and return the result. 26 | // const result = await tibberQuery.getConsumption(EnergyResolution.HOURLY, 10); 27 | const result = await tibberQuery.getConsumption(EnergyResolution.HOURLY, 10);// , '96a14971-525a-4420-aae9-e5aedaa129ff'); 28 | res.statusCode = 200; 29 | res.setHeader('Content-Type', 'application/json'); 30 | res.end(JSON.stringify(result)); 31 | }); 32 | 33 | // Start web server. 34 | server.listen(port, hostname, () => { 35 | console.log(`Server running at http://${hostname}:${port}/`); 36 | }); 37 | -------------------------------------------------------------------------------- /examples/get-energy-prices.ts: -------------------------------------------------------------------------------- 1 | // Uncomment the following line to include tibber-api NPM package instead. 2 | // const TibberQuery = require("tibber-api").TibberQuery; 3 | 4 | import { TibberQuery, IConfig } from '../src/index'; 5 | import http from 'http'; 6 | 7 | const hostname = '127.0.0.1'; 8 | const port = 3000; 9 | 10 | // Config object needed when instantiating TibberQuery 11 | const config: IConfig = { 12 | active: true, 13 | apiEndpoint: { 14 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 15 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 16 | }, 17 | }; 18 | 19 | // Instance of TibberQuery 20 | const tibberQuery = new TibberQuery(config); 21 | 22 | // Simple web server. 23 | const server = http.createServer(async (req, res) => { 24 | // Call the Tibber API and return the result. 25 | // const result = await tibberQuery.getTodaysEnergyPrices('96a14971-525a-4420-aae9-e5aedaa129ff'); 26 | // const result = await tibberQuery.getTomorrowsEnergyPrices('96a14971-525a-4420-aae9-e5aedaa129ff'); 27 | // const result = await tibberQuery.getCurrentEnergyPrices(); 28 | const result = await tibberQuery.getCurrentEnergyPrice('96a14971-525a-4420-aae9-e5aedaa129ff'); 29 | res.statusCode = 200; 30 | res.setHeader('Content-Type', 'application/json'); 31 | res.end(JSON.stringify(result)); 32 | }); 33 | 34 | // Start web server. 35 | server.listen(port, hostname, () => { 36 | console.log(`Server running at http://${hostname}:${port}/`); 37 | }); 38 | -------------------------------------------------------------------------------- /examples/get-homes-json.ts: -------------------------------------------------------------------------------- 1 | // Uncomment the following line to include tibber-api NPM package instead. 2 | // const TibberQuery = require("tibber-api").TibberQuery; 3 | 4 | import { TibberQuery, IConfig } from '../src/index'; 5 | import http from 'http'; 6 | 7 | const hostname = '127.0.0.1'; 8 | const port = 3000; 9 | 10 | // Config object needed when instantiating TibberQuery 11 | const config: IConfig = { 12 | active: true, 13 | apiEndpoint: { 14 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 15 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 16 | }, 17 | }; 18 | 19 | // Instance of TibberQuery 20 | const tibberQuery = new TibberQuery(config); 21 | 22 | // Simple web server. 23 | const server = http.createServer(async (req, res) => { 24 | // Call the Tibber API and return the result. 25 | const result = await tibberQuery.getHomes(); 26 | res.statusCode = 200; 27 | res.setHeader('Content-Type', 'application/json'); 28 | res.end(JSON.stringify(result)); 29 | }); 30 | 31 | // Start web server. 32 | server.listen(port, hostname, () => { 33 | console.log(`Server running at http://${hostname}:${port}/`); 34 | }); 35 | -------------------------------------------------------------------------------- /examples/get-homes.ts: -------------------------------------------------------------------------------- 1 | // Uncomment the following line to include tibber-api NPM package instead. 2 | // const TibberQuery = require("tibber-api").TibberQuery; 3 | 4 | import { TibberQuery, IConfig } from '../src/index'; 5 | import http from 'http'; 6 | 7 | const hostname = '127.0.0.1'; 8 | const port = 3000; 9 | 10 | // Config object needed when instantiating TibberQuery 11 | const config: IConfig = { 12 | active: true, 13 | apiEndpoint: { 14 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 15 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 16 | }, 17 | }; 18 | 19 | // GraphQL query 20 | const queryHomes = 'query{viewer{homes{id size appNickname appAvatar address{address1 address2 address3 postalCode city country latitude longitude}}}}'; 21 | 22 | // Instance of TibberQuery 23 | const tibberQuery = new TibberQuery(config); 24 | 25 | // Simple web server. 26 | const server = http.createServer(async (req, res) => { 27 | // Call the Tibber API and return the result. 28 | const result = await tibberQuery.query(queryHomes); 29 | res.statusCode = 200; 30 | res.setHeader('Content-Type', 'text/html'); 31 | res.write('

Hello Tibber API

'); 32 | res.write('Home Id: ' + result.viewer.homes[0].id); 33 | res.write('
Address: ' + result.viewer.homes[0].address.address1); 34 | res.end(''); 35 | }); 36 | 37 | // Start web server. 38 | server.listen(port, hostname, () => { 39 | console.log(`Server running at http://${hostname}:${port}/`); 40 | }); 41 | -------------------------------------------------------------------------------- /examples/push-notification.ts: -------------------------------------------------------------------------------- 1 | // Uncomment the following line to include tibber-api NPM package instead. 2 | // const TibberQuery = require("tibber-api").TibberQuery; 3 | 4 | import { TibberQuery, IConfig } from '../src/index'; 5 | import http from 'http'; 6 | import { AppScreen } from '../src/models/enums/AppScreen'; 7 | 8 | const hostname = '127.0.0.1'; 9 | const port = 3000; 10 | 11 | // Config object needed when instantiating TibberQuery 12 | const config: IConfig = { 13 | active: true, 14 | apiEndpoint: { 15 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 16 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 17 | }, 18 | }; 19 | 20 | // Instance of TibberQuery 21 | const tibberQuery = new TibberQuery(config); 22 | // your push config 23 | const message = 'TEST_MESSAGE'; 24 | const title = 'TEST_TITLE'; 25 | const screenToOpen = AppScreen.HOME; 26 | 27 | // Simple web server. 28 | const server = http.createServer(async (req, res) => { 29 | // Call the Tibber API and return the result. 30 | switch (req.url) { 31 | case '/push': 32 | const result = await tibberQuery.sendPushNotification(message, title, screenToOpen); 33 | res.statusCode = 200; 34 | res.setHeader('Content-Type', 'application/json'); 35 | res.end(JSON.stringify(result)); 36 | console.log(JSON.stringify(result)); 37 | break; 38 | } 39 | }); 40 | 41 | // Start web server. 42 | server.listen(port, hostname, () => { 43 | console.log(`Server running at http://${hostname}:${port}/`); 44 | }); 45 | -------------------------------------------------------------------------------- /examples/tibber-feed-full.ts: -------------------------------------------------------------------------------- 1 | import * as url from 'url'; 2 | import { TibberFeed, TibberQuery, TibberQueryBase } from './../src/index'; 3 | import { IConfig } from './../src/models/IConfig'; 4 | 5 | // Config object needed when instantiating TibberQuery 6 | const config: IConfig = { 7 | // Endpoint configuration. 8 | active: true, 9 | apiEndpoint: { 10 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 11 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 12 | }, 13 | // Query configuration. 14 | homeId: '96a14971-525a-4420-aae9-e5aedaa129ff', 15 | timestamp: true, 16 | power: true, 17 | lastMeterConsumption: true, 18 | accumulatedConsumption: true, 19 | accumulatedProduction: true, 20 | accumulatedCost: true, 21 | accumulatedReward: true, 22 | currency: true, 23 | minPower: true, 24 | averagePower: true, 25 | maxPower: true, 26 | powerProduction: false, 27 | minPowerProduction: false, 28 | maxPowerProduction: false, 29 | lastMeterProduction: false, 30 | powerFactor: true, 31 | voltagePhase1: true, 32 | voltagePhase2: true, 33 | voltagePhase3: true, 34 | currentL1: true, 35 | currentL2: true, 36 | currentL3: true, 37 | signalStrength: true, 38 | }; 39 | 40 | export class FakeTibberQuery extends TibberQueryBase { 41 | public override async getWebsocketSubscriptionUrl(): Promise { 42 | return new Promise((resolve, reject) => { 43 | resolve(new url.URL('wss://vg.no/')) 44 | }); 45 | } 46 | } 47 | 48 | const tibberQuery = new TibberQuery(config); 49 | const tibberFeed = new TibberFeed(tibberQuery, 5000); 50 | tibberFeed.on('connected', () => { 51 | console.log('Connected to Tibber!'); 52 | }); 53 | tibberFeed.on('connection_ack', () => { 54 | console.log('Connection acknowledged!'); 55 | }); 56 | tibberFeed.on('disconnected', async () => { 57 | console.log('Disconnected from Tibber!'); 58 | }); 59 | tibberFeed.on('error', async error => { 60 | console.error(error); 61 | }); 62 | tibberFeed.on('warn', warn => { 63 | console.warn(warn); 64 | }); 65 | tibberFeed.on('log', log => { 66 | console.log(log); 67 | }); 68 | tibberFeed.on('data', data => { 69 | console.log(data); 70 | }); 71 | 72 | setInterval(async () => { 73 | if (!tibberFeed.connected) 74 | await tibberFeed.connect(); 75 | 76 | }, 1000); -------------------------------------------------------------------------------- /examples/tibber-feed-simple.ts: -------------------------------------------------------------------------------- 1 | // Uncomment the following line to include tibber-api NPM package instead. 2 | // const TibberFeed = require("tibber-api").TibberFeed; 3 | 4 | import { TibberFeed, IConfig, TibberQuery } from '../src/index'; 5 | 6 | // Config object needed when instantiating TibberQuery 7 | const config: IConfig = { 8 | // Endpoint configuration. 9 | active: true, 10 | apiEndpoint: { 11 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 12 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 13 | }, 14 | // Query configuration. 15 | homeId: '96a14971-525a-4420-aae9-e5aedaa129ff', 16 | timestamp: true, 17 | power: true, 18 | }; 19 | 20 | const tibberQuery = new TibberQuery(config); 21 | const tibberFeed = new TibberFeed(tibberQuery); 22 | let counter = 0; 23 | // Subscribe to "data" event. 24 | tibberFeed.on('data', data => { 25 | // Close connection after receiving more tham 10 messages. 26 | if (counter++ >= 5) { 27 | tibberFeed.close(); 28 | } 29 | console.log(counter + ' - ' + JSON.stringify(data)); 30 | }); 31 | 32 | tibberFeed.on('connecting', data => { 33 | console.log(data); 34 | }); 35 | 36 | tibberFeed.on('connected', data => { 37 | console.log(data); 38 | }); 39 | 40 | tibberFeed.on('disconnecting', data => { 41 | console.log(data); 42 | }); 43 | 44 | tibberFeed.on('disconnected', data => { 45 | console.log(data); 46 | }); 47 | 48 | async function app() { 49 | // Connect to Tibber data feed 50 | await tibberFeed.connect(); 51 | console.log('Complete!'); 52 | } 53 | 54 | app(); 55 | -------------------------------------------------------------------------------- /jestconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.(t|j)sx?$": "ts-jest" 4 | }, 5 | "testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 6 | "moduleFileExtensions": [ 7 | "ts", 8 | "tsx", 9 | "js", 10 | "jsx", 11 | "json", 12 | "node" 13 | ], 14 | "testSequencer": "./test-sequencer.js", 15 | "detectOpenHandles": true 16 | } -------------------------------------------------------------------------------- /jsdoc/TibberQueryBase.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Class: TibberQueryBase 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Class: TibberQueryBase

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 |

TibberQueryBase()

32 | 33 | 34 |
35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 |

new TibberQueryBase()

45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
Source:
91 |
94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 |
124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 |

Methods

141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 |

JsonTryParse(input)

149 | 150 | 151 | 152 | 153 | 154 | 155 |
156 | Try to parse a string and return a valid JSON object. 157 | If string is not valid JSON, it will return an empty object instead. 158 |
159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 |
Parameters:
169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 |
NameTypeDescription
input 197 | 198 | Input string to try to parse as a JSON object
210 | 211 | 212 | 213 | 214 | 215 | 216 |
217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 |
Source:
244 |
247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 |
255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 |
Returns:
271 | 272 | 273 |
274 | Parsed or empty Json object 275 |
276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 |

(async) getRealTimeEnabled(homeId)

292 | 293 | 294 | 295 | 296 | 297 | 298 |
299 | Get selected home with some selected properties, including address and owner. 300 |
301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 |
Parameters:
311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 |
NameTypeDescription
homeId 339 | 340 | Tibber home ID
352 | 353 | 354 | 355 | 356 | 357 | 358 |
359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 |
Source:
386 |
389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 |
397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 |
Returns:
413 | 414 | 415 |
416 | IHome object 417 |
418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 |

getRequestOptions(method, uri)

434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 |
Parameters:
449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 |
NameTypeDescription
method 477 | 478 | HTTP method to use
uri 495 | 496 | Uri to use
508 | 509 | 510 | 511 | 512 | 513 | 514 |
515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 |
Source:
542 |
545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 |
553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 |
Returns:
569 | 570 | 571 |
572 | An object containing request options 573 |
574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 |

(async) getWebsocketSubscriptionUrl(homeId)

590 | 591 | 592 | 593 | 594 | 595 | 596 |
597 | Get selected home with some selected properties, including address and owner. 598 |
599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 |
Parameters:
609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 |
NameTypeDescription
homeId 637 | 638 | Tibber home ID
650 | 651 | 652 | 653 | 654 | 655 | 656 |
657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 |
Source:
684 |
687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 |
695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 |
Returns:
711 | 712 | 713 |
714 | IHome object 715 |
716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 |

(async) query(query, variables)

732 | 733 | 734 | 735 | 736 | 737 | 738 |
739 | General GQL query 740 |
741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 |
Parameters:
751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 |
NameTypeDescription
query 779 | 780 | GQL query.
variables 797 | 798 | Variables used by query parameter.
810 | 811 | 812 | 813 | 814 | 815 | 816 |
817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 |
Source:
844 |
847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 |
855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 |
Returns:
871 | 872 | 873 |
874 | Query result as JSON data 875 |
876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 |
892 | 893 |
894 | 895 | 896 | 897 | 898 |
899 | 900 | 903 | 904 |
905 | 906 |
907 | Documentation generated by JSDoc 4.0.0 on Mon Jan 02 2023 08:57:07 GMT+0000 (Coordinated Universal Time) 908 |
909 | 910 | 911 | 912 | 913 | -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /jsdoc/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bisand/tibber-api/82e072bab37fd534ebf9d700bb56fb9177bb9bfb/jsdoc/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /jsdoc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Home 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Home

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | 52 | 55 | 56 |
57 | 58 |
59 | Documentation generated by JSDoc 4.0.0 on Mon Jan 02 2023 08:57:07 GMT+0000 (Coordinated Universal Time) 60 |
61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /jsdoc/models_enums_PriceLevel.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: models/enums/PriceLevel.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: models/enums/PriceLevel.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
"use strict";
30 | Object.defineProperty(exports, "__esModule", { value: true });
31 | exports.PriceLevel = void 0;
32 | var PriceLevel;
33 | (function (PriceLevel) {
34 |     /** The price is greater than 90 % and smaller than 115 % compared to average price. */
35 |     PriceLevel["NORMAL"] = "NORMAL";
36 |     /** The price is greater than 60 % and smaller or equal to 90 % compared to average price. */
37 |     PriceLevel["CHEAP"] = "CHEAP";
38 |     /** The price is smaller or equal to 60 % compared to average price. */
39 |     PriceLevel["VERY_CHEAP"] = "VERY_CHEAP";
40 |     /** The price is greater or equal to 115 % and smaller than 140 % compared to average price. */
41 |     PriceLevel["EXPENSIVE"] = "EXPENSIVE";
42 |     /** The price is greater or equal to 140 % compared to average price. */
43 |     PriceLevel["VERY_EXPENSIVE"] = "VERY_EXPENSIVE";
44 | })(PriceLevel = exports.PriceLevel || (exports.PriceLevel = {}));
45 | //# sourceMappingURL=PriceLevel.js.map
46 |
47 |
48 | 49 | 50 | 51 | 52 |
53 | 54 | 57 | 58 |
59 | 60 |
61 | Documentation generated by JSDoc 4.0.0 on Mon Jan 02 2023 08:57:07 GMT+0000 (Coordinated Universal Time) 62 |
63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /jsdoc/models_enums_PriceRatingLevel.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: models/enums/PriceRatingLevel.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: models/enums/PriceRatingLevel.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
"use strict";
30 | Object.defineProperty(exports, "__esModule", { value: true });
31 | exports.PriceRatingLevel = void 0;
32 | var PriceRatingLevel;
33 | (function (PriceRatingLevel) {
34 |     /** The price is within the range of what is considered being normal (market dependent; see ‘priceRating.thresholdPercentages’ for limits) */
35 |     PriceRatingLevel["NORMAL"] = "NORMAL";
36 |     /** The price is within the range of what is considered being low (market dependent; see ‘priceRating.thresholdPercentages’ for limits) */
37 |     PriceRatingLevel["LOW"] = "LOW";
38 |     /** The price is within the range of what is considered being high (market dependent; see ‘priceRating.thresholdPercentages’ for limits) */
39 |     PriceRatingLevel["HIGH"] = "HIGH";
40 | })(PriceRatingLevel = exports.PriceRatingLevel || (exports.PriceRatingLevel = {}));
41 | //# sourceMappingURL=PriceRatingLevel.js.map
42 |
43 |
44 | 45 | 46 | 47 | 48 |
49 | 50 | 53 | 54 |
55 | 56 |
57 | Documentation generated by JSDoc 4.0.0 on Mon Jan 02 2023 08:57:07 GMT+0000 (Coordinated Universal Time) 58 |
59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /jsdoc/nodes_TibberQuery.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: nodes/TibberQuery.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: nodes/TibberQuery.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
"use strict";
 30 | Object.defineProperty(exports, "__esModule", { value: true });
 31 | exports.TibberQuery = void 0;
 32 | const consumption_gql_1 = require("../gql/consumption.gql");
 33 | const homes_gql_1 = require("../gql/homes.gql");
 34 | const home_gql_1 = require("../gql/home.gql");
 35 | const energy_gql_1 = require("../gql/energy.gql");
 36 | const sendPushNotification_gql_1 = require("../gql/sendPushNotification.gql");
 37 | const TibberQueryBase_1 = require("./TibberQueryBase");
 38 | class TibberQuery extends TibberQueryBase_1.TibberQueryBase {
 39 |     /**
 40 |      * Constructor
 41 |      * Create an instace of TibberQuery class
 42 |      * @param config IConfig object
 43 |      * @see IConfig
 44 |      */
 45 |     constructor(config) {
 46 |         super(config);
 47 |     }
 48 |     /**
 49 |      * Get selected home with some selected properties, including address and owner.
 50 |      * @param homeId Tibber home ID
 51 |      * @return IHome object
 52 |      */
 53 |     async getHome(homeId) {
 54 |         const variables = { homeId };
 55 |         const result = await this.query(home_gql_1.gqlHome, variables);
 56 |         if (result && result.viewer && result.viewer.home) {
 57 |             return Object.assign({}, result.viewer.home);
 58 |         }
 59 |         return result && result.error ? result : {};
 60 |     }
 61 |     /**
 62 |      * Get homes with all properties, including energy price, consumption and production.
 63 |      * @param homeId Tibber home ID
 64 |      * @return IHome object
 65 |      */
 66 |     async getHomeComplete(homeId) {
 67 |         const variables = { homeId };
 68 |         const result = await this.query(home_gql_1.gqlHomeComplete, variables);
 69 |         if (result && result.viewer && result.viewer.home) {
 70 |             return Object.assign({}, result.viewer.home);
 71 |         }
 72 |         return result && result.error ? result : {};
 73 |     }
 74 |     /**
 75 |      * Get homes with some selected properties, including address and owner.
 76 |      * @return Array of IHome.
 77 |      */
 78 |     async getHomes() {
 79 |         const result = await this.query(homes_gql_1.gqlHomes);
 80 |         if (result && result.viewer && Array.isArray(result.viewer.homes)) {
 81 |             return Object.assign([], result.viewer.homes);
 82 |         }
 83 |         return result && result.error ? result : {};
 84 |     }
 85 |     /**
 86 |      * Get homes with all properties, including energy price, consumption and production.
 87 |      * @return Array of IHome
 88 |      */
 89 |     async getHomesComplete() {
 90 |         const result = await this.query(homes_gql_1.gqlHomesComplete);
 91 |         if (result && result.viewer && Array.isArray(result.viewer.homes)) {
 92 |             return Object.assign([], result.viewer.homes);
 93 |         }
 94 |         return result && result.error ? result : {};
 95 |     }
 96 |     /**
 97 |      * Get current energy price for selected home.
 98 |      * @param homeId Tibber home ID
 99 |      * @return IPrice object
100 |      */
101 |     async getCurrentEnergyPrice(homeId) {
102 |         const variables = { homeId };
103 |         const result = await this.query(energy_gql_1.gqlCurrentEnergyPrice, variables);
104 |         if (result && result.viewer && result.viewer.home) {
105 |             const home = result.viewer.home;
106 |             return Object.assign({}, home.currentSubscription && home.currentSubscription.priceInfo ? home.currentSubscription.priceInfo.current : {});
107 |         }
108 |         return result && result.error ? result : {};
109 |     }
110 |     /**
111 |      * Get current energy prices from all homes registered to current user
112 |      * @return Array of IPrice
113 |      */
114 |     async getCurrentEnergyPrices() {
115 |         const result = await this.query(energy_gql_1.gqlCurrentEnergyPrices);
116 |         if (result && result.viewer && Array.isArray(result.viewer.homes)) {
117 |             const homes = result.viewer.homes;
118 |             const prices = homes.map((item) => {
119 |                 if (item && item.currentSubscription && item.currentSubscription.priceInfo && item.currentSubscription.priceInfo.current) {
120 |                     const price = item.currentSubscription.priceInfo.current;
121 |                     price.homeId = item.id;
122 |                     return price;
123 |                 }
124 |             });
125 |             return Object.assign([], prices);
126 |         }
127 |         return result && result.error ? result : {};
128 |     }
129 |     /**
130 |      * Get energy prices for today.
131 |      * @param homeId Tibber home ID
132 |      * @return Array of IPrice
133 |      */
134 |     async getTodaysEnergyPrices(homeId) {
135 |         const variables = { homeId };
136 |         const result = await this.query(energy_gql_1.gqlTodaysEnergyPrices, variables);
137 |         if (result && result.viewer && result.viewer.home) {
138 |             const data = result.viewer.home;
139 |             return Object.assign([], data.currentSubscription && data.currentSubscription.priceInfo ? data.currentSubscription.priceInfo.today : {});
140 |         }
141 |         return result && result.error ? result : {};
142 |     }
143 |     /**
144 |      * Get energy prices for tomorrow. These will only be available between 12:00 and 23:59
145 |      * @param homeId Tibber home ID
146 |      * @return Array of IPrice
147 |      */
148 |     async getTomorrowsEnergyPrices(homeId) {
149 |         const variables = { homeId };
150 |         const result = await this.query(energy_gql_1.gqlTomorrowsEnergyPrices, variables);
151 |         if (result && result.viewer && result.viewer.home) {
152 |             const data = result.viewer.home;
153 |             return Object.assign([], data.currentSubscription && data.currentSubscription.priceInfo ? data.currentSubscription.priceInfo.tomorrow : {});
154 |         }
155 |         return result && result.error ? result : {};
156 |     }
157 |     /**
158 |      * Get energy consumption for one or more homes.
159 |      * Returns an array of IConsumption
160 |      * @param resolution EnergyResolution. Valid values: HOURLY, DAILY, WEEKLY, MONTHLY, ANNUAL
161 |      * @param lastCount Return the last number of records
162 |      * @param homeId Tibber home ID. Optional parameter. Empty parameter will return all registered homes.
163 |      * @return Array of IConsumption
164 |      */
165 |     async getConsumption(resolution, lastCount, homeId) {
166 |         const variables = { homeId, resolution, lastCount };
167 |         if (homeId) {
168 |             const result = await this.query(consumption_gql_1.gqlHomeConsumption, variables);
169 |             if (result && result.viewer && result.viewer.home) {
170 |                 const home = result.viewer.home;
171 |                 return Object.assign([], home.consumption ? home.consumption.nodes : []);
172 |             }
173 |             return result && result.error ? result : { error: 'An error occurred while loadnig consumption.' };
174 |         }
175 |         else {
176 |             const result = await this.query(consumption_gql_1.gqlHomesConsumption, variables);
177 |             if (result && result.viewer && Array.isArray(result.viewer.homes)) {
178 |                 const consumptions = result.viewer.homes.map((item) => {
179 |                     const nodes = item.consumption.nodes.map((node) => {
180 |                         node.homeId = item.id;
181 |                         return node;
182 |                     });
183 |                     return nodes;
184 |                 });
185 |                 return Object.assign([], consumptions);
186 |             }
187 |             return result && result.error ? result : { error: 'An error occurred while loadnig consumption.' };
188 |         }
189 |     }
190 |     /**
191 |      * Sends a push notification to the current user's tibber app.
192 |      * Returns a ISendPushNotification Object
193 |      * @param title: "The title of your message";
194 |      * @param message: "The message you want to send";
195 |      * @param screen: AppScreen Object, example: AppScreen.HOME ;
196 |      * @return ISendPushNotification Object
197 |      */
198 |     async sendPushNotification(message, title, screen) {
199 |         const messagePayloadVariables = {
200 |             input: { title, message, screenToOpen: screen },
201 |         };
202 |         const result = await this.query(sendPushNotification_gql_1.gqlSendPushNotification, messagePayloadVariables);
203 |         if (result.sendPushNotification || result.errors) {
204 |             return Object.assign({}, result);
205 |         }
206 |         else
207 |             return Object.assign({}, { errors: [{ message: 'Undefined error' }] });
208 |     }
209 | }
210 | exports.TibberQuery = TibberQuery;
211 | //# sourceMappingURL=TibberQuery.js.map
212 |
213 |
214 | 215 | 216 | 217 | 218 |
219 | 220 | 223 | 224 |
225 | 226 |
227 | Documentation generated by JSDoc 4.0.0 on Mon Jan 02 2023 08:57:07 GMT+0000 (Coordinated Universal Time) 228 |
229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /jsdoc/nodes_TibberQueryBase.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Source: nodes/TibberQueryBase.js 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Source: nodes/TibberQueryBase.js

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
"use strict";
 30 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
 31 |     if (k2 === undefined) k2 = k;
 32 |     var desc = Object.getOwnPropertyDescriptor(m, k);
 33 |     if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
 34 |       desc = { enumerable: true, get: function() { return m[k]; } };
 35 |     }
 36 |     Object.defineProperty(o, k2, desc);
 37 | }) : (function(o, m, k, k2) {
 38 |     if (k2 === undefined) k2 = k;
 39 |     o[k2] = m[k];
 40 | }));
 41 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
 42 |     Object.defineProperty(o, "default", { enumerable: true, value: v });
 43 | }) : function(o, v) {
 44 |     o["default"] = v;
 45 | });
 46 | var __importStar = (this && this.__importStar) || function (mod) {
 47 |     if (mod && mod.__esModule) return mod;
 48 |     var result = {};
 49 |     if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
 50 |     __setModuleDefault(result, mod);
 51 |     return result;
 52 | };
 53 | var __importDefault = (this && this.__importDefault) || function (mod) {
 54 |     return (mod && mod.__esModule) ? mod : { "default": mod };
 55 | };
 56 | Object.defineProperty(exports, "__esModule", { value: true });
 57 | exports.TibberQueryBase = void 0;
 58 | const url = __importStar(require("url"));
 59 | const https_1 = __importDefault(require("https"));
 60 | const http_1 = __importDefault(require("http"));
 61 | const HttpMethod_1 = require("./models/HttpMethod");
 62 | const websocketSubscriptionUrl_1 = require("../gql/websocketSubscriptionUrl");
 63 | const Version_1 = require("../../Version");
 64 | const home_gql_1 = require("../gql/home.gql");
 65 | const TimeoutError_1 = require("./models/TimeoutError");
 66 | class TibberQueryBase {
 67 |     /**
 68 |      *
 69 |      */
 70 |     constructor(config) {
 71 |         var _a, _b, _c, _d;
 72 |         this.active = false;
 73 |         this._config = config;
 74 |         this._requestTimeout = Number((_b = (_a = this._config) === null || _a === void 0 ? void 0 : _a.apiEndpoint) === null || _b === void 0 ? void 0 : _b.requestTimeout) > 0 ? Number((_d = (_c = this._config) === null || _c === void 0 ? void 0 : _c.apiEndpoint) === null || _d === void 0 ? void 0 : _d.requestTimeout) : 5000;
 75 |     }
 76 |     get config() {
 77 |         return this._config;
 78 |     }
 79 |     set config(value) {
 80 |         this._config = value;
 81 |     }
 82 |     /**
 83 |      * Try to parse a string and return a valid JSON object.
 84 |      * If string is not valid JSON, it will return an empty object instead.
 85 |      * @param input Input string to try to parse as a JSON object
 86 |      * @returns Parsed or empty Json object
 87 |      */
 88 |     JsonTryParse(input) {
 89 |         try {
 90 |             // check if the string exists
 91 |             if (input) {
 92 |                 const o = JSON.parse(input);
 93 |                 // validate the result too
 94 |                 if (o && o.constructor === Object) {
 95 |                     return o;
 96 |                 }
 97 |             }
 98 |         }
 99 |         catch (e) {
100 |             // TODO: Add logging.
101 |         }
102 |         return { responseMessage: input };
103 |     }
104 |     ;
105 |     /**
106 |      *
107 |      * @param method HTTP method to use
108 |      * @param uri Uri to use
109 |      * @returns An object containing request options
110 |      */
111 |     getRequestOptions(method, uri) {
112 |         var _a;
113 |         return {
114 |             host: uri.host,
115 |             port: uri.port,
116 |             path: uri.path,
117 |             protocol: uri.protocol,
118 |             method,
119 |             headers: {
120 |                 Connection: 'Keep-Alive',
121 |                 Accept: 'application/json',
122 |                 Host: uri.hostname,
123 |                 'User-Agent': (`${(_a = this._config.apiEndpoint.userAgent) !== null && _a !== void 0 ? _a : ''} bisand/tibber-api/${Version_1.version}`).trim(),
124 |                 'Content-Type': 'application/json',
125 |                 Authorization: `Bearer ${this._config.apiEndpoint.apiKey}`,
126 |             },
127 |         };
128 |     }
129 |     /**
130 |      * General GQL query
131 |      * @param query GQL query.
132 |      * @param variables Variables used by query parameter.
133 |      * @return Query result as JSON data
134 |      */
135 |     async query(query, variables) {
136 |         return await new Promise((resolve, reject) => {
137 |             try {
138 |                 const uri = url.parse(this._config.apiEndpoint.queryUrl, true);
139 |                 const options = this.getRequestOptions(HttpMethod_1.HttpMethod.Post, uri);
140 |                 const data = new TextEncoder().encode(JSON.stringify({
141 |                     query,
142 |                     variables,
143 |                 }));
144 |                 const client = (uri.protocol === "https:") ? https_1.default : http_1.default;
145 |                 const req = client.request(options, (res) => {
146 |                     let str = '';
147 |                     res.on('data', (chunk) => {
148 |                         str += chunk;
149 |                     });
150 |                     res.on('end', () => {
151 |                         const response = this.JsonTryParse(str);
152 |                         const statusCode = Number(res === null || res === void 0 ? void 0 : res.statusCode);
153 |                         if (statusCode >= 200 && statusCode < 300) {
154 |                             resolve(response.data ? response.data : response);
155 |                         }
156 |                         else {
157 |                             response.httpCode = res === null || res === void 0 ? void 0 : res.statusCode;
158 |                             response.statusCode = res === null || res === void 0 ? void 0 : res.statusCode;
159 |                             response.statusMessage = res === null || res === void 0 ? void 0 : res.statusMessage;
160 |                             reject(response);
161 |                         }
162 |                         req.destroy();
163 |                     });
164 |                 });
165 |                 req.on('error', (e) => {
166 |                     reject(e);
167 |                 });
168 |                 req.setTimeout(this._requestTimeout, () => {
169 |                     req.destroy(new TimeoutError_1.TimeoutError(`Request imeout for uri ${uri}`));
170 |                 });
171 |                 if (data) {
172 |                     req.write(data);
173 |                 }
174 |                 req.end();
175 |             }
176 |             catch (error) {
177 |                 reject(error);
178 |             }
179 |         });
180 |     }
181 |     /**
182 |      * Get selected home with some selected properties, including address and owner.
183 |      * @param homeId Tibber home ID
184 |      * @return IHome object
185 |      */
186 |     async getWebsocketSubscriptionUrl() {
187 |         const result = await this.query(websocketSubscriptionUrl_1.qglWebsocketSubscriptionUrl);
188 |         if (result && result.viewer && result.viewer.websocketSubscriptionUrl) {
189 |             return new url.URL(result.viewer.websocketSubscriptionUrl);
190 |         }
191 |         return result && result.error ? result : {};
192 |     }
193 |     /**
194 |      * Get selected home with some selected properties, including address and owner.
195 |      * @param homeId Tibber home ID
196 |      * @return IHome object
197 |      */
198 |     async getRealTimeEnabled(homeId) {
199 |         var _a, _b, _c, _d;
200 |         const variables = { homeId };
201 |         const result = await this.query(home_gql_1.gqlHomeRealTime, variables);
202 |         if (result && result.viewer && result.viewer.home) {
203 |             return (_d = (_c = (_b = (_a = result === null || result === void 0 ? void 0 : result.viewer) === null || _a === void 0 ? void 0 : _a.home) === null || _b === void 0 ? void 0 : _b.features) === null || _c === void 0 ? void 0 : _c.realTimeConsumptionEnabled) !== null && _d !== void 0 ? _d : false;
204 |         }
205 |         return false;
206 |     }
207 | }
208 | exports.TibberQueryBase = TibberQueryBase;
209 | //# sourceMappingURL=TibberQueryBase.js.map
210 |
211 |
212 | 213 | 214 | 215 | 216 |
217 | 218 | 221 | 222 |
223 | 224 |
225 | Documentation generated by JSDoc 4.0.0 on Mon Jan 02 2023 08:57:07 GMT+0000 (Coordinated Universal Time) 226 |
227 | 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /jsdoc/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (() => { 3 | const source = document.getElementsByClassName('prettyprint source linenums'); 4 | let i = 0; 5 | let lineNumber = 0; 6 | let lineId; 7 | let lines; 8 | let totalLines; 9 | let anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = `line${lineNumber}`; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /jsdoc/scripts/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /jsdoc/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /jsdoc/scripts/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p th:last-child { border-right: 1px solid #ddd; } 224 | 225 | .ancestors, .attribs { color: #999; } 226 | .ancestors a, .attribs a 227 | { 228 | color: #999 !important; 229 | text-decoration: none; 230 | } 231 | 232 | .clear 233 | { 234 | clear: both; 235 | } 236 | 237 | .important 238 | { 239 | font-weight: bold; 240 | color: #950B02; 241 | } 242 | 243 | .yes-def { 244 | text-indent: -1000px; 245 | } 246 | 247 | .type-signature { 248 | color: #aaa; 249 | } 250 | 251 | .name, .signature { 252 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 253 | } 254 | 255 | .details { margin-top: 14px; border-left: 2px solid #DDD; } 256 | .details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; } 257 | .details dd { margin-left: 70px; } 258 | .details ul { margin: 0; } 259 | .details ul { list-style-type: none; } 260 | .details li { margin-left: 30px; padding-top: 6px; } 261 | .details pre.prettyprint { margin: 0 } 262 | .details .object-value { padding-top: 0; } 263 | 264 | .description { 265 | margin-bottom: 1em; 266 | margin-top: 1em; 267 | } 268 | 269 | .code-caption 270 | { 271 | font-style: italic; 272 | font-size: 107%; 273 | margin: 0; 274 | } 275 | 276 | .source 277 | { 278 | border: 1px solid #ddd; 279 | width: 80%; 280 | overflow: auto; 281 | } 282 | 283 | .prettyprint.source { 284 | width: inherit; 285 | } 286 | 287 | .source code 288 | { 289 | font-size: 100%; 290 | line-height: 18px; 291 | display: block; 292 | padding: 4px 12px; 293 | margin: 0; 294 | background-color: #fff; 295 | color: #4D4E53; 296 | } 297 | 298 | .prettyprint code span.line 299 | { 300 | display: inline-block; 301 | } 302 | 303 | .prettyprint.linenums 304 | { 305 | padding-left: 70px; 306 | -webkit-user-select: none; 307 | -moz-user-select: none; 308 | -ms-user-select: none; 309 | user-select: none; 310 | } 311 | 312 | .prettyprint.linenums ol 313 | { 314 | padding-left: 0; 315 | } 316 | 317 | .prettyprint.linenums li 318 | { 319 | border-left: 3px #ddd solid; 320 | } 321 | 322 | .prettyprint.linenums li.selected, 323 | .prettyprint.linenums li.selected * 324 | { 325 | background-color: lightyellow; 326 | } 327 | 328 | .prettyprint.linenums li * 329 | { 330 | -webkit-user-select: text; 331 | -moz-user-select: text; 332 | -ms-user-select: text; 333 | user-select: text; 334 | } 335 | 336 | .params .name, .props .name, .name code { 337 | color: #4D4E53; 338 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 339 | font-size: 100%; 340 | } 341 | 342 | .params td.description > p:first-child, 343 | .props td.description > p:first-child 344 | { 345 | margin-top: 0; 346 | padding-top: 0; 347 | } 348 | 349 | .params td.description > p:last-child, 350 | .props td.description > p:last-child 351 | { 352 | margin-bottom: 0; 353 | padding-bottom: 0; 354 | } 355 | 356 | .disabled { 357 | color: #454545; 358 | } 359 | -------------------------------------------------------------------------------- /jsdoc/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: #006400; 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /jsdoc/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tibber-api", 3 | "version": "5.4.2", 4 | "description": "Node.js module for connecting to Tibber API and extract data from your connected homes, including realtime data from Tibber Pulse.", 5 | "main": "lib/src/index.js", 6 | "types": "lib/src/index.d.ts", 7 | "files": [ 8 | "lib/**/*" 9 | ], 10 | "scripts": { 11 | "precompile": "mkver", 12 | "compile": "tsc", 13 | "prebuild": "mkver", 14 | "build": "tsc", 15 | "test": "jest --runInBand --config jestconfig.json", 16 | "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"", 17 | "lint": "tslint -p tsconfig.json", 18 | "jsdoc": "tsc && ./node_modules/.bin/jsdoc ./lib/src -r -d ./jsdoc", 19 | "prepare": "tsc", 20 | "preversion": "npm test", 21 | "version": "", 22 | "postversion": "npm run build" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/bisand/tibber-api.git" 27 | }, 28 | "dependencies": { 29 | "ws": "^8.18.1" 30 | }, 31 | "devDependencies": { 32 | "@jest/test-sequencer": "^29.7.0", 33 | "@types/jest": "^29.5.14", 34 | "@types/node": "^22.15.1", 35 | "@types/ws": "^8.18.1", 36 | "@typescript-eslint/eslint-plugin": "^8.31.0", 37 | "@typescript-eslint/parser": "^8.31.0", 38 | "eslint": "^9.25.1", 39 | "jest": "^29.7.0", 40 | "jsdoc": "^4.0.4", 41 | "jsdoc-to-markdown": "^9.1.1", 42 | "mkver": "^3.0.2", 43 | "nock": "^14.0.4", 44 | "prettier": "^3.5.3", 45 | "ts-jest": "^29.3.2", 46 | "typescript": "^5.8.3", 47 | "websocket": "^1.0.35" 48 | }, 49 | "directories": { 50 | "nodes": "nodes" 51 | }, 52 | "keywords": [ 53 | "tibber", 54 | "tibber-pulse", 55 | "iot", 56 | "power", 57 | "home-automation", 58 | "smarthome", 59 | "energy" 60 | ], 61 | "author": "André Biseth", 62 | "license": "MIT", 63 | "bugs": { 64 | "url": "https://github.com/bisand/tibber-api/issues" 65 | }, 66 | "homepage": "https://github.com/bisand/tibber-api#readme" 67 | } 68 | -------------------------------------------------------------------------------- /src/gql/consumption.gql.ts: -------------------------------------------------------------------------------- 1 | export const gqlHomesConsumption = ` 2 | query getConsumption($resolution: EnergyResolution! $lastCount:Int!){ 3 | viewer { 4 | homes { 5 | id 6 | consumption(resolution: $resolution, last: $lastCount) { 7 | nodes { 8 | from 9 | to 10 | cost 11 | unitPrice 12 | unitPriceVAT 13 | consumption 14 | consumptionUnit 15 | totalCost 16 | unitCost 17 | currency 18 | } 19 | } 20 | } 21 | } 22 | } 23 | `; 24 | 25 | export const gqlHomeConsumption = ` 26 | query getConsumption($homeId:ID! $resolution: EnergyResolution! $lastCount:Int!){ 27 | viewer { 28 | home(id:$homeId) { 29 | id 30 | consumption(resolution: $resolution, last: $lastCount) { 31 | nodes { 32 | from 33 | to 34 | cost 35 | unitPrice 36 | unitPriceVAT 37 | consumption 38 | consumptionUnit 39 | totalCost 40 | unitCost 41 | currency 42 | } 43 | } 44 | } 45 | } 46 | } 47 | `; 48 | -------------------------------------------------------------------------------- /src/gql/energy.gql.ts: -------------------------------------------------------------------------------- 1 | export const gqlCurrentEnergyPrices = ` 2 | query getCurrentEnergyPrice { 3 | viewer { 4 | homes { 5 | id 6 | currentSubscription{ 7 | id 8 | validFrom 9 | validTo 10 | status 11 | priceInfo{ 12 | current{ 13 | total 14 | energy 15 | tax 16 | startsAt 17 | currency 18 | level 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | `; 26 | 27 | export const gqlCurrentEnergyPrice = ` 28 | query getCurrentEnergyPrice($homeId:ID!) { 29 | viewer { 30 | home(id:$homeId) { 31 | id 32 | currentSubscription{ 33 | id 34 | validFrom 35 | validTo 36 | status 37 | priceInfo{ 38 | current{ 39 | total 40 | energy 41 | tax 42 | startsAt 43 | currency 44 | level 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | `; 52 | 53 | export const gqlTodaysEnergyPrices = ` 54 | query getTodaysEnergyPrices($homeId:ID!) { 55 | viewer { 56 | home(id:$homeId) { 57 | id 58 | currentSubscription { 59 | id 60 | validFrom 61 | validTo 62 | status 63 | priceInfo { 64 | today { 65 | total 66 | energy 67 | tax 68 | startsAt 69 | currency 70 | level 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | `; 78 | 79 | export const gqlTomorrowsEnergyPrices = ` 80 | query getTomorrowsEnergyPrices($homeId:ID!) { 81 | viewer { 82 | home(id:$homeId) { 83 | id 84 | currentSubscription { 85 | id 86 | validFrom 87 | validTo 88 | status 89 | priceInfo { 90 | tomorrow { 91 | total 92 | energy 93 | tax 94 | startsAt 95 | currency 96 | level 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | `; 104 | -------------------------------------------------------------------------------- /src/gql/home.gql.ts: -------------------------------------------------------------------------------- 1 | export const gqlHome = ` 2 | query getHome($homeId:ID!) { 3 | viewer { 4 | home(id:$homeId) { 5 | id 6 | timeZone 7 | appNickname 8 | appAvatar 9 | size 10 | type 11 | numberOfResidents 12 | primaryHeatingSource 13 | hasVentilationSystem 14 | mainFuseSize 15 | address { 16 | address1 17 | address2 18 | address3 19 | postalCode 20 | city 21 | country 22 | latitude 23 | longitude 24 | } 25 | owner { 26 | id 27 | firstName 28 | isCompany 29 | name 30 | middleName 31 | lastName 32 | organizationNo 33 | language 34 | contactInfo { 35 | email 36 | mobile 37 | } 38 | } 39 | meteringPointData { 40 | consumptionEan 41 | gridCompany 42 | gridAreaCode 43 | priceAreaCode 44 | productionEan 45 | energyTaxType 46 | vatType 47 | estimatedAnnualConsumption 48 | } 49 | features { 50 | realTimeConsumptionEnabled 51 | } 52 | } 53 | } 54 | } 55 | `; 56 | 57 | export const gqlHomeRealTime = ` 58 | query getHome($homeId:ID!) { 59 | viewer { 60 | home(id:$homeId) { 61 | features { 62 | realTimeConsumptionEnabled 63 | } 64 | } 65 | } 66 | } 67 | `; 68 | 69 | export const gqlHomeComplete = ` 70 | query getHomeComplete($homeId:ID!) { 71 | viewer { 72 | home(id:$homeId) { 73 | id 74 | timeZone 75 | appNickname 76 | appAvatar 77 | size 78 | type 79 | numberOfResidents 80 | primaryHeatingSource 81 | hasVentilationSystem 82 | mainFuseSize 83 | address { 84 | address1 85 | address2 86 | address3 87 | postalCode 88 | city 89 | country 90 | latitude 91 | longitude 92 | } 93 | owner { 94 | id 95 | firstName 96 | isCompany 97 | name 98 | middleName 99 | lastName 100 | organizationNo 101 | language 102 | contactInfo { 103 | email 104 | mobile 105 | } 106 | } 107 | meteringPointData { 108 | consumptionEan 109 | gridCompany 110 | gridAreaCode 111 | priceAreaCode 112 | productionEan 113 | energyTaxType 114 | vatType 115 | estimatedAnnualConsumption 116 | } 117 | currentSubscription { 118 | id 119 | subscriber { 120 | id 121 | firstName 122 | isCompany 123 | name 124 | middleName 125 | lastName 126 | organizationNo 127 | language 128 | contactInfo { 129 | email 130 | mobile 131 | } 132 | } 133 | validFrom 134 | validTo 135 | status 136 | priceInfo { 137 | current { 138 | total 139 | energy 140 | tax 141 | startsAt 142 | level 143 | currency 144 | } 145 | today { 146 | total 147 | energy 148 | tax 149 | startsAt 150 | level 151 | currency 152 | } 153 | tomorrow { 154 | total 155 | energy 156 | tax 157 | startsAt 158 | level 159 | currency 160 | } 161 | } 162 | } 163 | subscriptions { 164 | id 165 | subscriber { 166 | id 167 | } 168 | validFrom 169 | validTo 170 | status 171 | priceInfo { 172 | current { 173 | total 174 | energy 175 | tax 176 | startsAt 177 | level 178 | currency 179 | } 180 | today { 181 | total 182 | energy 183 | tax 184 | startsAt 185 | level 186 | currency 187 | } 188 | tomorrow { 189 | total 190 | energy 191 | tax 192 | startsAt 193 | level 194 | currency 195 | } 196 | } 197 | } 198 | features { 199 | realTimeConsumptionEnabled 200 | } 201 | } 202 | } 203 | } 204 | `; 205 | -------------------------------------------------------------------------------- /src/gql/homes.gql.ts: -------------------------------------------------------------------------------- 1 | export const gqlHomes = ` 2 | query getHomes { 3 | viewer { 4 | homes { 5 | id 6 | timeZone 7 | appNickname 8 | appAvatar 9 | size 10 | type 11 | numberOfResidents 12 | primaryHeatingSource 13 | hasVentilationSystem 14 | mainFuseSize 15 | address { 16 | address1 17 | address2 18 | address3 19 | postalCode 20 | city 21 | country 22 | latitude 23 | longitude 24 | } 25 | owner { 26 | id 27 | firstName 28 | isCompany 29 | name 30 | middleName 31 | lastName 32 | organizationNo 33 | language 34 | contactInfo { 35 | email 36 | mobile 37 | } 38 | } 39 | meteringPointData { 40 | consumptionEan 41 | gridCompany 42 | gridAreaCode 43 | priceAreaCode 44 | productionEan 45 | energyTaxType 46 | vatType 47 | estimatedAnnualConsumption 48 | } 49 | features { 50 | realTimeConsumptionEnabled 51 | } 52 | } 53 | } 54 | } 55 | `; 56 | 57 | export const gqlHomesComplete = ` 58 | query getHomesComplete { 59 | viewer { 60 | homes { 61 | id 62 | timeZone 63 | appNickname 64 | appAvatar 65 | size 66 | type 67 | numberOfResidents 68 | primaryHeatingSource 69 | hasVentilationSystem 70 | mainFuseSize 71 | address { 72 | address1 73 | address2 74 | address3 75 | postalCode 76 | city 77 | country 78 | latitude 79 | longitude 80 | } 81 | owner { 82 | id 83 | firstName 84 | isCompany 85 | name 86 | middleName 87 | lastName 88 | organizationNo 89 | language 90 | contactInfo { 91 | email 92 | mobile 93 | } 94 | } 95 | meteringPointData { 96 | consumptionEan 97 | gridCompany 98 | gridAreaCode 99 | priceAreaCode 100 | productionEan 101 | energyTaxType 102 | vatType 103 | estimatedAnnualConsumption 104 | } 105 | currentSubscription { 106 | id 107 | subscriber { 108 | id 109 | firstName 110 | isCompany 111 | name 112 | middleName 113 | lastName 114 | organizationNo 115 | language 116 | contactInfo { 117 | email 118 | mobile 119 | } 120 | } 121 | validFrom 122 | validTo 123 | status 124 | priceInfo { 125 | current { 126 | total 127 | energy 128 | tax 129 | startsAt 130 | level 131 | currency 132 | } 133 | today { 134 | total 135 | energy 136 | tax 137 | startsAt 138 | level 139 | currency 140 | } 141 | tomorrow { 142 | total 143 | energy 144 | tax 145 | startsAt 146 | level 147 | currency 148 | } 149 | } 150 | } 151 | subscriptions { 152 | id 153 | subscriber { 154 | id 155 | } 156 | validFrom 157 | validTo 158 | status 159 | priceInfo { 160 | current { 161 | total 162 | energy 163 | tax 164 | startsAt 165 | level 166 | currency 167 | } 168 | today { 169 | total 170 | energy 171 | tax 172 | startsAt 173 | level 174 | currency 175 | } 176 | tomorrow { 177 | total 178 | energy 179 | tax 180 | startsAt 181 | level 182 | currency 183 | } 184 | } 185 | } 186 | features { 187 | realTimeConsumptionEnabled 188 | } 189 | } 190 | } 191 | } 192 | `; 193 | -------------------------------------------------------------------------------- /src/gql/sendPushNotification.gql.ts: -------------------------------------------------------------------------------- 1 | export const gqlSendPushNotification = ` 2 | mutation sendPushNotification($input: PushNotificationInput!) { 3 | sendPushNotification(input: $input) { 4 | successful 5 | pushedToNumberOfDevices 6 | } 7 | } 8 | `; 9 | -------------------------------------------------------------------------------- /src/gql/websocketSubscriptionUrl.ts: -------------------------------------------------------------------------------- 1 | export const qglWebsocketSubscriptionUrl = ` 2 | query getWebsocketSubscriptionUrl { 3 | viewer { 4 | websocketSubscriptionUrl 5 | } 6 | } 7 | `; 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { TibberFeed } from './nodes/TibberFeed'; 2 | import { TibberQuery } from './nodes/TibberQuery'; 3 | import { TibberQueryBase } from './nodes/TibberQueryBase'; 4 | import { UrlTools } from './nodes/models/tools'; 5 | import { IConfig } from './models/IConfig'; 6 | 7 | export { TibberFeed, TibberQuery, TibberQueryBase, UrlTools, IConfig as IConfig }; 8 | -------------------------------------------------------------------------------- /src/models/IAddress.ts: -------------------------------------------------------------------------------- 1 | export interface IAddress { 2 | address1: string; 3 | address2: string; 4 | address3: string; 5 | postalCode: string; 6 | city: string; 7 | country: string; 8 | latitude: string; 9 | longitude: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/models/IConfig.ts: -------------------------------------------------------------------------------- 1 | import { IEndpoint } from './IEndpoint'; 2 | 3 | export interface IConfig { 4 | active: boolean; 5 | // Endpoint configuration. 6 | apiEndpoint: IEndpoint; 7 | // Query configuration. 8 | homeId?: string; 9 | timestamp?: boolean; 10 | power?: boolean; 11 | lastMeterConsumption?: boolean; 12 | accumulatedConsumption?: boolean; 13 | accumulatedProduction?: boolean; 14 | accumulatedProductionLastHour?: boolean; 15 | accumulatedConsumptionLastHour?: boolean; 16 | accumulatedCost?: boolean; 17 | accumulatedReward?: boolean; 18 | currency?: boolean; 19 | minPower?: boolean; 20 | averagePower?: boolean; 21 | maxPower?: boolean; 22 | powerProduction?: boolean; 23 | minPowerProduction?: boolean; 24 | maxPowerProduction?: boolean; 25 | lastMeterProduction?: boolean; 26 | powerFactor?: boolean; 27 | voltagePhase1?: boolean; 28 | voltagePhase2?: boolean; 29 | voltagePhase3?: boolean; 30 | currentL1?: boolean; 31 | currentL2?: boolean; 32 | currentL3?: boolean; 33 | signalStrength?: boolean; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/models/IConsumption.ts: -------------------------------------------------------------------------------- 1 | export interface IConsumption { 2 | homeId?: string; 3 | from: string; 4 | to: string; 5 | unitPrice: number; 6 | unitPriceVAT: number; 7 | consumption: number; 8 | consumptionUnit: string; 9 | totalCost: number; 10 | unitCost: number; 11 | cost: number; 12 | currency: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/models/IContactInfo.ts: -------------------------------------------------------------------------------- 1 | export interface IContactInfo { 2 | email: string; 3 | mobile: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/models/ICurrentSubscription.ts: -------------------------------------------------------------------------------- 1 | import { IPriceInfo } from './IPriceInfo'; 2 | export interface ICurrentSubscription { 3 | id: string; 4 | validFrom: string; 5 | validTo: null; 6 | status: string; 7 | priceInfo: IPriceInfo; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/IEndpoint.ts: -------------------------------------------------------------------------------- 1 | export interface IEndpoint { 2 | queryUrl: string; 3 | apiKey: string; 4 | /** 5 | * User agent string. Please comply with the RFC 7231 standard. 6 | * All text provided will be prepended with 'bisand/tibber-api/x.x.x' where x.x.x is the version of the library. 7 | * Once used in any instance of TibberQuery and TibberFeed, it will be sanitized and not change. 8 | * @example 'MyApp/1.0 (https://example.com)' 9 | * @returns 'MyApp/1.0 (https://example.com) bisand/tibber-api/x.x.x' 10 | * @default 'bisand/tibber-api/x.x.x' 11 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent} 12 | * @see {@link https://tools.ietf.org/html/rfc7231#section-5.5.3} 13 | * @see {@link https://tools.ietf.org/html/rfc7230#section-3.2.6} 14 | * @see {@link https://tools.ietf.org/html/rfc7230#section-5.5} 15 | * */ 16 | userAgent?: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/models/IErrors.ts: -------------------------------------------------------------------------------- 1 | export interface IErrors { 2 | message: string; 3 | locations: [ 4 | { 5 | line: number; 6 | column: number; 7 | }, 8 | ]; 9 | path: string[]; 10 | extensions: { 11 | code: string; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/models/IHome.ts: -------------------------------------------------------------------------------- 1 | import { IAddress } from './IAddress'; 2 | import { IMeteringPointData } from './IMeteringPointData'; 3 | import { IHomeFeatures } from "./IHomeFeatures"; 4 | import { ILegalEntity } from './ILegalEntity'; 5 | import { ISubscription } from './ISubscription'; 6 | import { HomeAvatar } from './enums/HomeAvatar'; 7 | import { HomeType } from './enums/HomeType'; 8 | import { HeatingSource } from './enums/HeatingSource'; 9 | import { IHomeConsumptionConnection } from './IHomeConsumptionConnection'; 10 | import { IHomeProductionConnection } from './IHomeProductionConnection'; 11 | export interface IHome { 12 | id?: string; 13 | timeZone?: string; 14 | appNickname?: string; 15 | appAvatar?: HomeAvatar; 16 | size?: number; 17 | type?: HomeType; 18 | numberOfResidents?: number; 19 | primaryHeatingSource?: HeatingSource; 20 | hasVentilationSystem?: boolean; 21 | mainFuseSize?: number; 22 | address?: IAddress; 23 | owner?: ILegalEntity; 24 | consumption?: IHomeConsumptionConnection; 25 | meteringPointData?: IMeteringPointData; 26 | currentSubscription?: ISubscription; 27 | subscriptions?: ISubscription[]; 28 | production?: IHomeProductionConnection; 29 | features?: IHomeFeatures; 30 | } 31 | -------------------------------------------------------------------------------- /src/models/IHomeConsumptionConnection.ts: -------------------------------------------------------------------------------- 1 | import { IConsumption } from './IConsumption'; 2 | import { IHomeConsumptionEdge } from "./IHomeConsumptionEdge"; 3 | import { IHomeConsumptionPageInfo } from "./IHomeConsumptionPageInfo"; 4 | export interface IHomeConsumptionConnection { 5 | pageInfo: IHomeConsumptionPageInfo; 6 | nodes: IConsumption[]; 7 | edges: IHomeConsumptionEdge[]; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/IHomeConsumptionEdge.ts: -------------------------------------------------------------------------------- 1 | import { IConsumption } from './IConsumption'; 2 | export interface IHomeConsumptionEdge { 3 | cursor: string; 4 | node: IConsumption; 5 | } 6 | -------------------------------------------------------------------------------- /src/models/IHomeConsumptionPageInfo.ts: -------------------------------------------------------------------------------- 1 | export interface IHomeConsumptionPageInfo { 2 | endCursor: string; 3 | hasNextPage: boolean; 4 | hasPreviousPage: boolean; 5 | startCursor: string; 6 | count: number; 7 | currency: string; 8 | totalCost: number; 9 | totalConsumption: number; 10 | filtered: number; 11 | } 12 | -------------------------------------------------------------------------------- /src/models/IHomeFeatures.ts: -------------------------------------------------------------------------------- 1 | export interface IHomeFeatures { 2 | realTimeConsumptionEnabled: boolean; 3 | } 4 | -------------------------------------------------------------------------------- /src/models/IHomeProductionConnection.ts: -------------------------------------------------------------------------------- 1 | import { IProduction } from "./IProduction"; 2 | import { IHomeProductionEdge } from "./IHomeProductionEdge"; 3 | import { IHomeProductionPageInfo } from "./IHomeProductionPageInfo"; 4 | export interface IHomeProductionConnection { 5 | pageInfo: IHomeProductionPageInfo; 6 | nodes: IProduction[]; 7 | edges: IHomeProductionEdge[]; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/IHomeProductionEdge.ts: -------------------------------------------------------------------------------- 1 | import { IProduction } from "./IProduction"; 2 | export interface IHomeProductionEdge { 3 | cursor: string; 4 | node: IProduction; 5 | } 6 | -------------------------------------------------------------------------------- /src/models/IHomeProductionPageInfo.ts: -------------------------------------------------------------------------------- 1 | export interface IHomeProductionPageInfo { 2 | endCursor: string; 3 | hasNextPage: boolean; 4 | hasPreviousPage: boolean; 5 | startCursor: string; 6 | count: number; 7 | currency: string; 8 | totalProfit: number; 9 | totalProduction: number; 10 | filtered: number; 11 | } 12 | -------------------------------------------------------------------------------- /src/models/ILegalEntity.ts: -------------------------------------------------------------------------------- 1 | import { IAddress } from './IAddress'; 2 | import { IContactInfo } from './IContactInfo'; 3 | export interface ILegalEntity { 4 | id: string; 5 | firstName: string; 6 | isCompany: boolean; 7 | name: string; 8 | middleName: string; 9 | lastName: string; 10 | organizationNo: string; 11 | language: string; 12 | contactInfo: IContactInfo; 13 | address: IAddress; 14 | } 15 | -------------------------------------------------------------------------------- /src/models/ILiveMeasurement.ts: -------------------------------------------------------------------------------- 1 | export interface ILiveMeasurement { 2 | timestamp: string; 3 | power: number; 4 | lastMeterConsumption: number; 5 | accumulatedConsumption: number; 6 | accumulatedProduction: number; 7 | accumulatedConsumptionLastHour: number; 8 | accumulatedProductionLastHour: number; 9 | accumulatedCost: number; 10 | accumulatedReward: number; 11 | currency: string; 12 | minPower: number; 13 | averagePower: number; 14 | maxPower: number; 15 | powerProduction: number; 16 | minPowerProduction: number; 17 | maxPowerProduction: number; 18 | lastMeterProduction: number; 19 | powerFactor: number; 20 | voltagePhase1: number; 21 | voltagePhase2: number; 22 | voltagePhase3: number; 23 | currentL1: number; 24 | currentL2: number; 25 | currentL3: number; 26 | signalStrength: number; 27 | } 28 | -------------------------------------------------------------------------------- /src/models/IMeteringPointData.ts: -------------------------------------------------------------------------------- 1 | export interface IMeteringPointData { 2 | consumptionEan: string; 3 | gridCompany: string; 4 | gridAreaCode: string; 5 | priceAreaCode: string; 6 | productionEan: null; 7 | energyTaxType: string; 8 | vatType: string; 9 | estimatedAnnualConsumption: number; 10 | } 11 | -------------------------------------------------------------------------------- /src/models/IPrice.ts: -------------------------------------------------------------------------------- 1 | import { PriceLevel } from './enums/PriceLevel'; 2 | export interface IPrice { 3 | homeId?: string; 4 | total?: number; 5 | energy?: number; 6 | tax?: number; 7 | startsAt?: string; 8 | level?: PriceLevel; 9 | } 10 | -------------------------------------------------------------------------------- /src/models/IPriceInfo.ts: -------------------------------------------------------------------------------- 1 | import { IPrice } from './IPrice'; 2 | import { ISubscriptionPriceConnection } from './ISubscriptionPriceConnection'; 3 | export interface IPriceInfo { 4 | current?: IPrice; 5 | today?: IPrice[]; 6 | tomorrow?: IPrice[]; 7 | range?: ISubscriptionPriceConnection; // Should be SubscriptionPriceConnection 8 | } 9 | -------------------------------------------------------------------------------- /src/models/IPriceRating.ts: -------------------------------------------------------------------------------- 1 | import { PriceRatingThresholdPercentages } from './PriceRatingThresholdPercentages'; 2 | import { PriceRatingType } from './PriceRatingType'; 3 | 4 | export interface IPriceRating { 5 | /** The different ‘high’/‘low’ price breakpoints (market dependent) */ 6 | thresholdPercentages: PriceRatingThresholdPercentages; 7 | /** The hourly prices of today, the previous 7 days, and tomorrow */ 8 | hourly: PriceRatingType; 9 | /** The daily prices of today and the previous 30 days */ 10 | daily: PriceRatingType; 11 | /** The monthly prices of this month and the previous 31 months */ 12 | monthly: PriceRatingType; 13 | } 14 | -------------------------------------------------------------------------------- /src/models/IProduction.ts: -------------------------------------------------------------------------------- 1 | export interface IProduction { 2 | from: string; 3 | to: string; 4 | unitPrice: number; 5 | unitPriceVAT: number; 6 | production: number; 7 | productionUnit: string; 8 | profit: number; 9 | currency: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/models/IQuery.ts: -------------------------------------------------------------------------------- 1 | import { IQueryPayload } from "./IQueryPayload"; 2 | 3 | export interface IQuery { 4 | id?: string | undefined | null; 5 | type: string; 6 | payload?: IQueryPayload | string | Record | undefined | null; 7 | } 8 | -------------------------------------------------------------------------------- /src/models/IQueryPayload.ts: -------------------------------------------------------------------------------- 1 | export interface IQueryPayload { 2 | token: string; 3 | operationName: string | null; 4 | query: string; 5 | variables: Record | null; 6 | extensions: Record | null; 7 | } 8 | -------------------------------------------------------------------------------- /src/models/ISendPushNotification.ts: -------------------------------------------------------------------------------- 1 | import { IErrors } from './IErrors'; 2 | 3 | export interface ISendPushNotification { 4 | sendPushNotification?: { 5 | successful: string; 6 | pushedToNumberOfDevices: number; 7 | }; 8 | errors?: IErrors[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/models/ISendPushNotificationPayload.ts: -------------------------------------------------------------------------------- 1 | import { AppScreen } from './enums/AppScreen'; 2 | 3 | export interface ISendPushNotificationPayload { 4 | input: { 5 | title: string; 6 | message: string; 7 | screenToOpen: AppScreen; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /src/models/ISubscription.ts: -------------------------------------------------------------------------------- 1 | import { IPriceInfo } from './IPriceInfo'; 2 | import { ILegalEntity } from './ILegalEntity'; 3 | import { IPriceRating } from './IPriceRating'; 4 | export interface ISubscription { 5 | id: string; 6 | /** The owner of the subscription */ 7 | subscriber: ILegalEntity; 8 | /** The time the subscription started */ 9 | validFrom: string; 10 | /** The time the subscription ended */ 11 | validTo: null; 12 | /** The current status of the subscription */ 13 | status: string; 14 | /** 15 | * Price information related to the subscription 16 | * @deprecated 17 | * @see priceRating should be used instead 18 | */ 19 | priceInfo: IPriceInfo; 20 | /** Price information related to the subscription */ 21 | priceRating: IPriceRating; 22 | } 23 | -------------------------------------------------------------------------------- /src/models/ISubscriptionPriceConnection.ts: -------------------------------------------------------------------------------- 1 | import { IPrice } from './IPrice'; 2 | import { ISubscriptionPriceEdge } from "./ISubscriptionPriceEdge"; 3 | import { ISubscriptionPriceConnectionPageInfo } from "./ISubscriptionPriceConnectionPageInfo"; 4 | export interface ISubscriptionPriceConnection { 5 | pageInfo: ISubscriptionPriceConnectionPageInfo; 6 | edges: ISubscriptionPriceEdge[]; 7 | nodes: IPrice[]; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/ISubscriptionPriceConnectionPageInfo.ts: -------------------------------------------------------------------------------- 1 | export interface ISubscriptionPriceConnectionPageInfo { 2 | endCursor: string; 3 | hasNextPage: boolean; 4 | hasPreviousPage: boolean; 5 | startCursor: string; 6 | resolution: string; 7 | currency: string; 8 | count: number; 9 | precision: string; 10 | minEnergy: number; 11 | minTotal: number; 12 | maxEnergy: number; 13 | maxTotal: number; 14 | } 15 | -------------------------------------------------------------------------------- /src/models/ISubscriptionPriceEdge.ts: -------------------------------------------------------------------------------- 1 | import { IPrice } from './IPrice'; 2 | export interface ISubscriptionPriceEdge { 3 | cursor: string; 4 | node: IPrice; 5 | } 6 | -------------------------------------------------------------------------------- /src/models/PriceRatingEntry.ts: -------------------------------------------------------------------------------- 1 | import { PriceRatingLevel } from "./enums/PriceRatingLevel"; 2 | 3 | 4 | export interface PriceRatingEntry { 5 | /** The start time of the price */ 6 | time: string; 7 | /** Nordpool spot price */ 8 | energy: number; 9 | /** The total price (incl. tax) */ 10 | total: number; 11 | /** The tax part of the price (guarantee of origin certificate, energy tax (Sweden only) and VAT) */ 12 | tax: number; 13 | /** The percentage difference compared to the trailing price average (1 day for ‘hourly’, 30 days for ‘daily’ and 32 months for ‘monthly’) */ 14 | difference: number; 15 | /** The price level compared to recent price values (calculated using ‘difference’ and ‘priceRating.thresholdPercentages’) */ 16 | level: PriceRatingLevel; 17 | } 18 | -------------------------------------------------------------------------------- /src/models/PriceRatingThresholdPercentages.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface PriceRatingThresholdPercentages { 3 | /** The percentage difference when the price is considered to be ‘high’ (market dependent) */ 4 | high: number; 5 | /** The percentage difference when the price is considered to be ‘low’ (market dependent) */ 6 | low: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/models/PriceRatingType.ts: -------------------------------------------------------------------------------- 1 | import { PriceRatingEntry } from "./PriceRatingEntry"; 2 | 3 | 4 | export interface PriceRatingType { 5 | /** Lowest Nordpool spot price over the time period */ 6 | minEnergy: number; 7 | /** Highest Nordpool spot price over the time period */ 8 | maxEnergy: number; 9 | /** Lowest total price (incl. tax) over the time period */ 10 | minTotal: number; 11 | /** Highest total price (incl. tax) over the time period */ 12 | maxTotal: number; 13 | /** The price currency */ 14 | currency: string; 15 | /** The individual price entries aggregated by hourly/daily/monthly values} */ 16 | entries: PriceRatingEntry[]; 17 | } 18 | -------------------------------------------------------------------------------- /src/models/enums/AppScreen.ts: -------------------------------------------------------------------------------- 1 | export enum AppScreen { 2 | HOME = 'HOME', 3 | REPORTS = 'REPORTS', 4 | CONSUMPTION = 'CONSUMPTION', 5 | COMPARISON = 'COMPARISON', 6 | DISAGGREGATION = 'DISAGGREGATION', 7 | HOME_PROFILE = 'HOME_PROFILE', 8 | CUSTOMER_PROFILE = 'CUSTOMER_PROFILE', 9 | METER_READING = 'METER_READING', 10 | NOTIFICATIONS = 'NOTIFICATIONS', 11 | INVOICES = 'INVOICES', 12 | } -------------------------------------------------------------------------------- /src/models/enums/EnergyResolution.ts: -------------------------------------------------------------------------------- 1 | export enum EnergyResolution { 2 | HOURLY = 'HOURLY', 3 | DAILY = 'DAILY', 4 | WEEKLY = 'WEEKLY', 5 | MONTHLY = 'MONTHLY', 6 | ANNUAL = 'ANNUAL', 7 | } 8 | -------------------------------------------------------------------------------- /src/models/enums/HeatingSource.ts: -------------------------------------------------------------------------------- 1 | export enum HeatingSource { 2 | AIR2AIR_HEATPUMP = 'AIR2AIR_HEATPUMP', 3 | ELECTRICITY = 'ELECTRICITY', 4 | GROUND = 'GROUND', 5 | DISTRICT_HEATING = 'DISTRICT_HEATING', 6 | ELECTRIC_BOILER = 'ELECTRIC_BOILER', 7 | AIR2WATER_HEATPUMP = 'AIR2WATER_HEATPUMP', 8 | OTHER = 'OTHER', 9 | } -------------------------------------------------------------------------------- /src/models/enums/HomeAvatar.ts: -------------------------------------------------------------------------------- 1 | export enum HomeAvatar { 2 | APARTMENT = 'APARTMENT', 3 | ROWHOUSE = 'ROWHOUSE', 4 | FLOORHOUSE1 = 'FLOORHOUSE1', 5 | FLOORHOUSE2 = 'FLOORHOUSE2', 6 | FLOORHOUSE3 = 'FLOORHOUSE3', 7 | COTTAGE = 'COTTAGE', 8 | CASTLE = 'CASTLE', 9 | } -------------------------------------------------------------------------------- /src/models/enums/HomeType.ts: -------------------------------------------------------------------------------- 1 | export enum HomeType { 2 | APARTMENT = 'APARTMENT', 3 | ROWHOUSE = 'ROWHOUSE', 4 | HOUSE = 'HOUSE', 5 | COTTAGE = 'COTTAGE', 6 | } -------------------------------------------------------------------------------- /src/models/enums/PriceLevel.ts: -------------------------------------------------------------------------------- 1 | export enum PriceLevel { 2 | /** The price is greater than 90 % and smaller than 115 % compared to average price. */ 3 | NORMAL = 'NORMAL', 4 | /** The price is greater than 60 % and smaller or equal to 90 % compared to average price. */ 5 | CHEAP = 'CHEAP', 6 | /** The price is smaller or equal to 60 % compared to average price. */ 7 | VERY_CHEAP = 'VERY_CHEAP', 8 | /** The price is greater or equal to 115 % and smaller than 140 % compared to average price. */ 9 | EXPENSIVE = 'EXPENSIVE', 10 | /** The price is greater or equal to 140 % compared to average price. */ 11 | VERY_EXPENSIVE = 'VERY_EXPENSIVE', 12 | } 13 | -------------------------------------------------------------------------------- /src/models/enums/PriceRatingLevel.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum PriceRatingLevel { 3 | /** The price is within the range of what is considered being normal (market dependent; see ‘priceRating.thresholdPercentages’ for limits) */ 4 | NORMAL = 'NORMAL', 5 | /** The price is within the range of what is considered being low (market dependent; see ‘priceRating.thresholdPercentages’ for limits) */ 6 | LOW = 'LOW', 7 | /** The price is within the range of what is considered being high (market dependent; see ‘priceRating.thresholdPercentages’ for limits) */ 8 | HIGH = 'HIGH' 9 | } 10 | -------------------------------------------------------------------------------- /src/models/enums/PriceResolution.ts: -------------------------------------------------------------------------------- 1 | export enum PriceResolution { 2 | HOURLY = 'HOURLY', 3 | DAILY = 'DAILY', 4 | } 5 | -------------------------------------------------------------------------------- /src/nodes/TibberQuery.ts: -------------------------------------------------------------------------------- 1 | import { IConfig } from '../models/IConfig'; 2 | import { IHome } from '../models/IHome'; 3 | import { IPrice } from '../models/IPrice'; 4 | import { EnergyResolution } from '../models/enums/EnergyResolution'; 5 | import { IConsumption } from '../models/IConsumption'; 6 | import { gqlHomesConsumption, gqlHomeConsumption } from '../gql/consumption.gql'; 7 | import { gqlHomes, gqlHomesComplete } from '../gql/homes.gql'; 8 | import { gqlHome, gqlHomeComplete } from '../gql/home.gql'; 9 | import { gqlCurrentEnergyPrice, gqlTodaysEnergyPrices, gqlTomorrowsEnergyPrices, gqlCurrentEnergyPrices } from '../gql/energy.gql'; 10 | import { gqlSendPushNotification } from '../gql/sendPushNotification.gql'; 11 | import { ISendPushNotification } from '../models/ISendPushNotification'; 12 | import { AppScreen } from '../models/enums/AppScreen'; 13 | import { TibberQueryBase } from './TibberQueryBase'; 14 | 15 | export class TibberQuery extends TibberQueryBase { 16 | /** 17 | * Constructor 18 | * Create an instace of TibberQuery class 19 | * @param {IConfig} config Config object 20 | * @param {number} requestTimeout Request timeout in milliseconds. 21 | * @see IConfig 22 | */ 23 | constructor(config: IConfig, requestTimeout: number = 30000) { 24 | super(config, requestTimeout); 25 | } 26 | 27 | /** 28 | * Get selected home with some selected properties, including address and owner. 29 | * @param homeId Tibber home ID 30 | * @return IHome object 31 | */ 32 | public async getHome(homeId: string): Promise { 33 | const variables = { homeId }; 34 | const result = await this.query(gqlHome, variables); 35 | if (result && result.viewer && result.viewer.home) { 36 | return Object.assign({} as IHome, result.viewer.home); 37 | } 38 | return result && result.error ? result : {}; 39 | } 40 | 41 | /** 42 | * Get homes with all properties, including energy price, consumption and production. 43 | * @param homeId Tibber home ID 44 | * @return IHome object 45 | */ 46 | public async getHomeComplete(homeId: string): Promise { 47 | const variables = { homeId }; 48 | const result = await this.query(gqlHomeComplete, variables); 49 | if (result && result.viewer && result.viewer.home) { 50 | return Object.assign({} as IHome, result.viewer.home); 51 | } 52 | return result && result.error ? result : {}; 53 | } 54 | 55 | /** 56 | * Get homes with some selected properties, including address and owner. 57 | * @return Array of IHome. 58 | */ 59 | public async getHomes(): Promise { 60 | const result = await this.query(gqlHomes); 61 | if (result && result.viewer && Array.isArray(result.viewer.homes)) { 62 | return Object.assign([] as IHome[], result.viewer.homes); 63 | } 64 | return result && result.error ? result : []; 65 | } 66 | 67 | /** 68 | * Get homes with all properties, including energy price, consumption and production. 69 | * @return Array of IHome 70 | */ 71 | public async getHomesComplete(): Promise { 72 | const result = await this.query(gqlHomesComplete); 73 | if (result && result.viewer && Array.isArray(result.viewer.homes)) { 74 | return Object.assign([] as IHome[], result.viewer.homes); 75 | } 76 | return result && result.error ? result : []; 77 | } 78 | 79 | /** 80 | * Get current energy price for selected home. 81 | * @param homeId Tibber home ID 82 | * @return IPrice object 83 | */ 84 | public async getCurrentEnergyPrice(homeId: string): Promise { 85 | const variables = { homeId }; 86 | const result = await this.query(gqlCurrentEnergyPrice, variables); 87 | if (result && result.viewer && result.viewer.home) { 88 | const home: IHome = result.viewer.home; 89 | return Object.assign( 90 | {} as IPrice, 91 | home.currentSubscription && home.currentSubscription.priceInfo ? home.currentSubscription.priceInfo.current : {}, 92 | ); 93 | } 94 | return result && result.error ? result : {}; 95 | } 96 | 97 | /** 98 | * Get current energy prices from all homes registered to current user 99 | * @return Array of IPrice 100 | */ 101 | public async getCurrentEnergyPrices(): Promise { 102 | const result = await this.query(gqlCurrentEnergyPrices); 103 | if (result && result.viewer && Array.isArray(result.viewer.homes)) { 104 | const homes: IHome[] = result.viewer.homes; 105 | const prices = homes.map((item: IHome) => { 106 | if (item && item.currentSubscription && item.currentSubscription.priceInfo && item.currentSubscription.priceInfo.current) { 107 | const price = item.currentSubscription.priceInfo.current; 108 | price.homeId = item.id; 109 | return price; 110 | } 111 | }); 112 | return Object.assign([] as IPrice[], prices); 113 | } 114 | return result && result.error ? result : []; 115 | } 116 | 117 | /** 118 | * Get energy prices for today. 119 | * @param homeId Tibber home ID 120 | * @return Array of IPrice 121 | */ 122 | public async getTodaysEnergyPrices(homeId: string): Promise { 123 | const variables = { homeId }; 124 | const result = await this.query(gqlTodaysEnergyPrices, variables); 125 | if (result && result.viewer && result.viewer.home) { 126 | const data: IHome = result.viewer.home; 127 | return Object.assign( 128 | [] as IPrice[], 129 | data.currentSubscription && data.currentSubscription.priceInfo ? data.currentSubscription.priceInfo.today : {}, 130 | ); 131 | } 132 | return result && result.error ? result : []; 133 | } 134 | 135 | /** 136 | * Get energy prices for tomorrow. These will only be available between 12:00 and 23:59 137 | * @param homeId Tibber home ID 138 | * @return Array of IPrice 139 | */ 140 | public async getTomorrowsEnergyPrices(homeId: string): Promise { 141 | const variables = { homeId }; 142 | const result = await this.query(gqlTomorrowsEnergyPrices, variables); 143 | if (result && result.viewer && result.viewer.home) { 144 | const data: IHome = result.viewer.home; 145 | return Object.assign( 146 | [] as IPrice[], 147 | data.currentSubscription && data.currentSubscription.priceInfo ? data.currentSubscription.priceInfo.tomorrow : {}, 148 | ); 149 | } 150 | return result && result.error ? result : []; 151 | } 152 | 153 | /** 154 | * Get energy consumption for one or more homes. 155 | * Returns an array of IConsumption 156 | * @param resolution EnergyResolution. Valid values: HOURLY, DAILY, WEEKLY, MONTHLY, ANNUAL 157 | * @param lastCount Return the last number of records 158 | * @param homeId Tibber home ID. Optional parameter. Empty parameter will return all registered homes. 159 | * @return Array of IConsumption 160 | */ 161 | public async getConsumption(resolution: EnergyResolution, lastCount: number, homeId?: string): Promise { 162 | const variables = { homeId, resolution, lastCount }; 163 | if (homeId) { 164 | const result = await this.query(gqlHomeConsumption, variables); 165 | if (result && result.viewer && result.viewer.home) { 166 | const home: IHome = result.viewer.home; 167 | return Object.assign([] as IConsumption[], home.consumption ? home.consumption.nodes : []); 168 | } 169 | return result && result.error ? result : []; 170 | } else { 171 | const result = await this.query(gqlHomesConsumption, variables); 172 | if (result && result.viewer && Array.isArray(result.viewer.homes)) { 173 | const consumptions = result.viewer.homes.map((item: IHome) => { 174 | const nodes = item.consumption?.nodes.map((node: IConsumption) => { 175 | node.homeId = item.id; 176 | return node; 177 | }); 178 | return nodes; 179 | }); 180 | return Object.assign([] as IConsumption[], consumptions); 181 | } 182 | return result && result.error ? result : []; 183 | } 184 | } 185 | 186 | /** 187 | * Sends a push notification to the current user's tibber app. 188 | * Returns a ISendPushNotification Object 189 | * @param title: "The title of your message"; 190 | * @param message: "The message you want to send"; 191 | * @param screen: AppScreen Object, example: AppScreen.HOME ; 192 | * @return ISendPushNotification Object 193 | */ 194 | public async sendPushNotification(message: string, title: string, screen: AppScreen): Promise { 195 | const messagePayloadVariables = { 196 | input: { title, message, screenToOpen: screen }, 197 | }; 198 | 199 | const result = await this.query(gqlSendPushNotification, messagePayloadVariables); 200 | 201 | if (result.sendPushNotification || result.errors) { 202 | return Object.assign({} as ISendPushNotification, result); 203 | } else return Object.assign({}, { errors: [{ message: 'Undefined error' }] } as ISendPushNotification); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/nodes/TibberQueryBase.ts: -------------------------------------------------------------------------------- 1 | import { IConfig } from '../models/IConfig' 2 | import * as url from 'url' 3 | import { HttpMethod } from './models/HttpMethod' 4 | import { qglWebsocketSubscriptionUrl } from '../gql/websocketSubscriptionUrl' 5 | import { gqlHomeRealTime } from '../gql/home.gql' 6 | import { TimeoutError } from './models/TimeoutError' 7 | import { HeaderManager } from '../tools/HeaderManager' 8 | 9 | import { IncomingMessage, request as httpRequest } from 'http'; 10 | import { request as httpsRequest } from 'https'; 11 | import { parse as parseUrl } from 'url'; 12 | import { RequestOptions } from 'http'; 13 | import { TextEncoder } from 'util'; // available in Node 12+ 14 | 15 | export class TibberQueryBase { 16 | public active: boolean 17 | private _config: IConfig 18 | private _headerManager: HeaderManager 19 | 20 | private _requestTimeout: number 21 | 22 | public get requestTimeout(): number { 23 | return this._requestTimeout 24 | } 25 | public set requestTimeout(value: number) { 26 | this._requestTimeout = value 27 | } 28 | 29 | public get config(): IConfig { 30 | return this._config 31 | } 32 | public set config(value: IConfig) { 33 | this._config = value 34 | } 35 | 36 | /** 37 | * 38 | */ 39 | constructor(config: IConfig, requestTimeout: number = 30000) { 40 | this.active = false 41 | this._config = config 42 | this._headerManager = new HeaderManager(config) 43 | this._requestTimeout = requestTimeout > 1000 ? requestTimeout : 1000 44 | } 45 | 46 | /** 47 | * Try to parse a string and return a valid JSON object. 48 | * If string is not valid JSON, it will return an empty object instead. 49 | * @param input Input string to try to parse as a JSON object 50 | * @returns Parsed or empty Json object 51 | */ 52 | protected JsonTryParse(input: string): object { 53 | try { 54 | // check if the string exists 55 | if (input) { 56 | const o = JSON.parse(input) 57 | 58 | // validate the result too 59 | if (o && o.constructor === Object) { 60 | return o 61 | } 62 | } 63 | } 64 | catch (e: any) { 65 | // TODO - Add logging. 66 | } 67 | 68 | return { responseMessage: input } 69 | } 70 | 71 | /** 72 | * 73 | * @param method HTTP method to use 74 | * @param uri Uri to use 75 | * @returns An object containing request options 76 | */ 77 | protected getRequestOptions(method: HttpMethod, uri: url.UrlWithStringQuery): RequestOptions { 78 | return { 79 | method, 80 | hostname: uri.hostname, 81 | port: uri.port, 82 | path: uri.path, 83 | headers: { 84 | Accept: 'application/json', 85 | Host: uri.hostname as string, 86 | 'User-Agent': this._headerManager.userAgent, 87 | 'Content-Type': 'application/json', 88 | Authorization: `Bearer ${this._config.apiEndpoint.apiKey}`, 89 | }, 90 | }; 91 | } 92 | 93 | /** 94 | * General GQL query 95 | * @param query GQL query. 96 | * @param variables Variables used by query parameter. 97 | * @return Query result as JSON data 98 | */ 99 | public async query(query: string, variables?: object): Promise { 100 | return new Promise((resolve, reject) => { 101 | try { 102 | const uri = parseUrl(this._config.apiEndpoint.queryUrl); 103 | const isHttps = uri.protocol === 'https:'; 104 | const client = isHttps ? httpsRequest : httpRequest; 105 | 106 | const payload = JSON.stringify({ query, variables }); 107 | const data = new TextEncoder().encode(payload); 108 | 109 | const options: RequestOptions = this.getRequestOptions(HttpMethod.Post, uri); 110 | const req = client(options, (res: IncomingMessage) => { 111 | const chunks: Buffer[] = []; 112 | 113 | res?.on('data', (chunk: Buffer) => { 114 | chunks.push(chunk); 115 | }); 116 | 117 | res?.on('end', () => { 118 | const body = Buffer.concat(chunks).toString('utf-8'); 119 | const parsed: any = this.JsonTryParse(body); 120 | const status = res?.statusCode ?? 0; 121 | 122 | if (status >= 200 && status < 300) { 123 | resolve(parsed.data ?? parsed); 124 | } else { 125 | parsed.httpCode = status; 126 | parsed.statusCode = res?.statusCode ?? 500; 127 | parsed.statusMessage = res?.statusMessage ?? 'No response received'; 128 | if (!body) { 129 | parsed.message = 'Empty response from server'; 130 | } 131 | reject(parsed); 132 | } 133 | }); 134 | }); 135 | 136 | req.on('error', (err) => { 137 | reject(err); 138 | }); 139 | 140 | req.setTimeout(this._requestTimeout, () => { 141 | req.destroy(new TimeoutError(`Request timeout for ${uri.href}`)); 142 | }); 143 | 144 | req.write(data); 145 | req.end(); 146 | } catch (err) { 147 | reject(err); 148 | } 149 | }); 150 | } 151 | 152 | /** 153 | * Get selected home with some selected properties, including address and owner. 154 | * @param homeId Tibber home ID 155 | * @return IHome object 156 | */ 157 | public async getWebsocketSubscriptionUrl(): Promise { 158 | const result = await this.query(qglWebsocketSubscriptionUrl) 159 | if (result && result.viewer && result.viewer.websocketSubscriptionUrl) { 160 | return new url.URL(result.viewer.websocketSubscriptionUrl) 161 | } 162 | throw new Error( 163 | result && result.error 164 | ? `Failed to get websocket subscription URL: ${result.error}` 165 | : 'Websocket subscription URL not found in response' 166 | ) 167 | } 168 | 169 | /** 170 | * Get selected home with some selected properties, including address and owner. 171 | * @param homeId Tibber home ID 172 | * @return IHome object 173 | */ 174 | public async getRealTimeEnabled(homeId: string): Promise { 175 | const variables = { homeId } 176 | const result = await this.query(gqlHomeRealTime, variables) 177 | return result?.viewer?.home?.features?.realTimeConsumptionEnabled ?? false 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/nodes/models/GQL.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum GQL { 3 | CONNECTION_INIT = 'connection_init', 4 | CONNECTION_ACK = 'connection_ack', 5 | CONNECTION_ERROR = 'connection_error', 6 | SUBSCRIBE = 'subscribe', 7 | COMPLETE = 'complete', 8 | NEXT = 'next', 9 | ERROR = 'error', 10 | } 11 | -------------------------------------------------------------------------------- /src/nodes/models/HttpMethod.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum HttpMethod { 3 | Get = "GET", 4 | Post = "POST" 5 | } 6 | -------------------------------------------------------------------------------- /src/nodes/models/TimeoutError.ts: -------------------------------------------------------------------------------- 1 | export class TimeoutError extends Error { 2 | constructor(message: string) { 3 | super(message); 4 | this.name = 'TimeoutError'; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/nodes/models/tools.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export class UrlTools { 3 | public validateUrl(url: string) { 4 | const regExp = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/; 5 | return new RegExp(regExp).test(url); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/tools/HeaderManager.ts: -------------------------------------------------------------------------------- 1 | import { version } from '../../Version'; 2 | import { IConfig } from '../models/IConfig'; 3 | 4 | export class HeaderManager { 5 | private static readonly DEFAULT_USER_AGENT = `bisand/tibber-api/${version}`; 6 | private static readonly USER_AGENT_MAX_LENGTH = 255 - HeaderManager.DEFAULT_USER_AGENT.length - 1; 7 | 8 | private static _userAgent: string | null = null; 9 | private _config: IConfig; 10 | 11 | /** 12 | * Constructor 13 | * Create an instance of TibberBase class 14 | * @param {IConfig} config Config object 15 | * @see IConfig 16 | */ 17 | constructor(config: IConfig) { 18 | // Clone the config object to avoid changing the original object 19 | this._config = JSON.parse(JSON.stringify(config)); 20 | } 21 | 22 | /** 23 | * Gets the User-Agent from apiEndpoint and ensures it is not changed once set. 24 | */ 25 | public get userAgent(): string { 26 | if (HeaderManager._userAgent === null) { 27 | HeaderManager._userAgent = `${this.sanitizeUserAgent(this._config.apiEndpoint.userAgent)} ${HeaderManager.DEFAULT_USER_AGENT}`.trim(); 28 | } 29 | return HeaderManager._userAgent; 30 | } 31 | 32 | /** 33 | * Sanitize User-Agent string. 34 | * - Remove all characters that are not allowed in User-Agent. 35 | * - Limit the length to 255 characters. 36 | * @param {string} userAgent User-Agent string to sanitize. 37 | * @returns {string} Sanitized User-Agent string. 38 | * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent} 39 | * @see {@link https://tools.ietf.org/html/rfc7231#section-5.5.3} 40 | * @see {@link https://tools.ietf.org/html/rfc7230#section-3.2.6} 41 | * @see {@link https://tools.ietf.org/html/rfc7230#section-5.5} 42 | * */ 43 | private sanitizeUserAgent(userAgent?: string): string { 44 | if (!userAgent) { 45 | return ''; 46 | } 47 | // Regex to match valid characters (printable ASCII excluding control characters) 48 | const validCharsRegex = /[^\x20-\x7E]+/g; 49 | // Remove invalid characters 50 | let sanitized = userAgent.replace(validCharsRegex, ''); 51 | // Normalize excessive spaces 52 | sanitized = sanitized.replace(/\s+/g, ' ').trim(); 53 | 54 | return sanitized.length > HeaderManager.USER_AGENT_MAX_LENGTH ? sanitized.substring(0, HeaderManager.USER_AGENT_MAX_LENGTH) : sanitized; 55 | } 56 | } -------------------------------------------------------------------------------- /test-sequencer.js: -------------------------------------------------------------------------------- 1 | const Sequencer = require('@jest/test-sequencer').default; 2 | 3 | class CustomSequencer extends Sequencer { 4 | sort(tests) { 5 | // Test structure information 6 | // https://github.com/facebook/jest/blob/6b8b1404a1d9254e7d5d90a8934087a9c9899dab/packages/jest-runner/src/types.ts#L17-L21 7 | const copyTests = Array.from(tests); 8 | return copyTests.sort((testA, testB) => (testA.path > testB.path ? 1 : -1)); 9 | } 10 | } 11 | 12 | module.exports = CustomSequencer; 13 | 14 | -------------------------------------------------------------------------------- /tests/test0-header-manager-tests/test1-sanitize-user-agent.ts: -------------------------------------------------------------------------------- 1 | import { HeaderManager } from '../../src/tools/HeaderManager'; 2 | import { IConfig } from '../../src/models/IConfig'; 3 | import { version } from '../../Version'; 4 | 5 | // FILE: src/nodes/HeaderManager.test.ts 6 | 7 | 8 | describe('HeaderManager', () => { 9 | const defaultUserAgent = `bisand/tibber-api/${version}`; 10 | const userAgentMaxLength = 255 - defaultUserAgent.length - 1; 11 | 12 | let config: IConfig; 13 | 14 | beforeEach(() => { 15 | config = { 16 | apiEndpoint: { 17 | userAgent: '' 18 | } 19 | } as IConfig; 20 | }); 21 | 22 | describe('sanitizeUserAgent', () => { 23 | it('should return the sanitized user agent', () => { 24 | const headerManager = new HeaderManager(config); 25 | expect(headerManager['sanitizeUserAgent']('ValidUserAgent')).toBe('ValidUserAgent'); 26 | }); 27 | 28 | it('should remove invalid characters from the user agent', () => { 29 | const headerManager = new HeaderManager(config); 30 | expect(headerManager['sanitizeUserAgent']('Invalid@UserAgent!function now()\n{ [native code] }')).toBe('Invalid@UserAgent!function now(){ [native code] }'); 31 | }); 32 | 33 | it('should truncate the user agent if it exceeds the maximum length', () => { 34 | const longUserAgent = 'a'.repeat(userAgentMaxLength + 10); 35 | const headerManager = new HeaderManager(config); 36 | expect(headerManager['sanitizeUserAgent'](longUserAgent)).toBe(longUserAgent.substring(0, userAgentMaxLength)); 37 | }); 38 | 39 | it('should return an empty string if the user agent is undefined', () => { 40 | const headerManager = new HeaderManager(config); 41 | expect(headerManager['sanitizeUserAgent']()).toBe(''); 42 | }); 43 | }); 44 | }); -------------------------------------------------------------------------------- /tests/test0-header-manager-tests/test2-valid-user-agent.ts: -------------------------------------------------------------------------------- 1 | import { HeaderManager } from '../../src/tools/HeaderManager'; 2 | import { IConfig } from '../../src/models/IConfig'; 3 | import { version } from '../../Version'; 4 | 5 | // FILE: src/nodes/HeaderManager.test.ts 6 | 7 | 8 | describe('HeaderManager', () => { 9 | const defaultUserAgent = `bisand/tibber-api/${version}`; 10 | const userAgentMaxLength = 255 - defaultUserAgent.length; 11 | 12 | let config: IConfig; 13 | 14 | beforeEach(() => { 15 | config = { 16 | apiEndpoint: { 17 | userAgent: '' 18 | } 19 | } as IConfig; 20 | }); 21 | 22 | describe('Valid User-Agent', () => { 23 | it('should return the sanitized user agent with default user agent appended', () => { 24 | config.apiEndpoint.userAgent = 'ValidUserAgent'; 25 | const headerManager = new HeaderManager(config); 26 | expect(headerManager.userAgent).toBe(`ValidUserAgent ${defaultUserAgent}`); 27 | }); 28 | }); 29 | }); -------------------------------------------------------------------------------- /tests/test0-header-manager-tests/test3-sanitized-user-agent.ts: -------------------------------------------------------------------------------- 1 | import { HeaderManager } from '../../src/tools/HeaderManager'; 2 | import { IConfig } from '../../src/models/IConfig'; 3 | import { version } from '../../Version'; 4 | 5 | // FILE: src/nodes/HeaderManager.test.ts 6 | 7 | 8 | describe('HeaderManager', () => { 9 | const defaultUserAgent = `bisand/tibber-api/${version}`; 10 | const userAgentMaxLength = 255 - defaultUserAgent.length - 1; 11 | 12 | let config: IConfig; 13 | 14 | beforeEach(() => { 15 | config = { 16 | apiEndpoint: { 17 | userAgent: '' 18 | } 19 | } as IConfig; 20 | }); 21 | 22 | describe('Sanitized invalid User-Agent', () => { 23 | it('should sanitize and return the user agent with default user agent appended', () => { 24 | config.apiEndpoint.userAgent = 'Invalid@UserAgent!function now()\n{ [native code] }'; 25 | const headerManager = new HeaderManager(config); 26 | expect(headerManager.userAgent).toBe(`Invalid@UserAgent!function now(){ [native code] } ${defaultUserAgent}`); 27 | }); 28 | }); 29 | }); -------------------------------------------------------------------------------- /tests/test0-header-manager-tests/test4-truncate-user-agent.ts: -------------------------------------------------------------------------------- 1 | import { HeaderManager } from '../../src/tools/HeaderManager'; 2 | import { IConfig } from '../../src/models/IConfig'; 3 | import { version } from '../../Version'; 4 | 5 | // FILE: src/nodes/HeaderManager.test.ts 6 | 7 | 8 | describe('HeaderManager', () => { 9 | const defaultUserAgent = `bisand/tibber-api/${version}`; 10 | const userAgentMaxLength = 255 - defaultUserAgent.length - 1; 11 | 12 | let config: IConfig; 13 | 14 | beforeEach(() => { 15 | config = { 16 | apiEndpoint: { 17 | userAgent: '' 18 | } 19 | } as IConfig; 20 | }); 21 | 22 | describe('Truncate long User-Agent', () => { 23 | it('should truncate the user agent if it exceeds the maximum length', () => { 24 | const longUserAgent = 'a'.repeat(userAgentMaxLength + 10); 25 | config.apiEndpoint.userAgent = longUserAgent; 26 | const headerManager = new HeaderManager(config); 27 | expect(headerManager.userAgent.length).toBe(255); 28 | }); 29 | }); 30 | }); -------------------------------------------------------------------------------- /tests/test0-header-manager-tests/test5-user-agent-set-only-once.ts: -------------------------------------------------------------------------------- 1 | import { HeaderManager } from '../../src/tools/HeaderManager'; 2 | import { IConfig } from '../../src/models/IConfig'; 3 | import { version } from '../../Version'; 4 | 5 | // FILE: src/nodes/HeaderManager.test.ts 6 | 7 | 8 | describe('HeaderManager', () => { 9 | const defaultUserAgent = `bisand/tibber-api/${version}`; 10 | const userAgentMaxLength = 255 - defaultUserAgent.length - 1; 11 | 12 | let config: IConfig; 13 | 14 | beforeEach(() => { 15 | config = { 16 | apiEndpoint: { 17 | userAgent: '' 18 | } 19 | } as IConfig; 20 | }); 21 | 22 | describe('User-Agent set only once', () => { 23 | it('it should only be alowed to set user-agent once', () => { 24 | config.apiEndpoint.userAgent = 'ValidUserAgent'; 25 | const headerManager = new HeaderManager(config); 26 | config.apiEndpoint.userAgent = 'AnotherUserAgent'; 27 | const headerManager2 = new HeaderManager(config); 28 | expect(headerManager.userAgent).toBe(`ValidUserAgent ${defaultUserAgent}`); 29 | expect(headerManager2.userAgent).toBe(`ValidUserAgent ${defaultUserAgent}`); 30 | }); 31 | }); 32 | }); -------------------------------------------------------------------------------- /tests/test1-integration-tibber-query.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import { TibberQuery, IConfig } from '../src/index'; 3 | 4 | const config: IConfig = { 5 | active: false, 6 | apiEndpoint: { 7 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 8 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 9 | }, 10 | }; 11 | 12 | // let tibberQuery: TibberQuery; 13 | 14 | beforeAll(() => { 15 | // tibberQuery = new TibberQuery(config); 16 | }); 17 | 18 | afterAll(async () => { 19 | await new Promise((resolve) => setTimeout(() => resolve(), 500)); // avoid jest open handle error 20 | }); 21 | 22 | afterEach(async () => { 23 | await new Promise((resolve) => setTimeout(() => resolve(), 500)); // avoid jest open handle error 24 | }); 25 | 26 | test('TibberQuery - Should be created', () => { 27 | expect(async () => { 28 | const query = new TibberQuery(config); 29 | return query; 30 | }).toBeDefined(); 31 | }); 32 | 33 | // test('TibberQuery.getWebsocketSubscriptionUrl() should be valid', async () => { 34 | // const url = await tibberQuery.getWebsocketSubscriptionUrl(); 35 | // expect(url).toBeDefined(); 36 | // const urlTools = new UrlTools(); 37 | // expect(urlTools.validateUrl(url.href)).toBe(true); 38 | // }, 30000); 39 | 40 | // test('TibberQuery.getHomes() should be valid', async () => { 41 | // const homes = await tibberQuery.getHomes(); 42 | // expect(homes).toBeDefined(); 43 | // expect(homes.length).toBeGreaterThan(0); 44 | // homes.forEach((home) => { 45 | // expect(home).toBeDefined(); 46 | // expect(home.address).toBeDefined(); 47 | // expect(home.owner).toBeDefined(); 48 | // expect(home.meteringPointData).toBeDefined(); 49 | // expect(home.features).toBeDefined(); 50 | // }); 51 | // }, 30000); 52 | 53 | // test('TibberQuery.getHomesComplete() should be valid', async () => { 54 | // const homes = await tibberQuery.getHomesComplete(); 55 | // expect(homes).toBeDefined(); 56 | // expect(homes.length).toBeGreaterThan(0); 57 | // homes.forEach((home) => { 58 | // expect(home).toBeDefined(); 59 | // expect(home.address).toBeDefined(); 60 | // expect(home.owner).toBeDefined(); 61 | // expect(home.meteringPointData).toBeDefined(); 62 | // expect(home.currentSubscription).toBeDefined(); 63 | // expect(home.subscriptions).toBeDefined(); 64 | // expect(home.features).toBeDefined(); 65 | // }); 66 | // }, 30000); 67 | 68 | // test('TibberQuery.getConsumption() with homeId should be valid', async () => { 69 | // const consumption = await tibberQuery.getConsumption(EnergyResolution.HOURLY, 10, '96a14971-525a-4420-aae9-e5aedaa129ff'); 70 | // expect(consumption).toBeDefined(); 71 | // expect(consumption.length).toEqual(10); 72 | // }, 30000); 73 | 74 | // test('TibberQuery.getConsumption() should be valid', async () => { 75 | // const consumption = await tibberQuery.getConsumption(EnergyResolution.HOURLY, 10); 76 | // expect(consumption).toBeDefined(); 77 | // consumption.forEach((con) => { 78 | // const conObj = Object.assign([] as IConsumption[], con); 79 | // expect(conObj.length).toEqual(10); 80 | // }); 81 | // }, 30000); 82 | 83 | // test('TibberQuery.getCurrentEnergyPrice() should be valid', async () => { 84 | // const price = await tibberQuery.getCurrentEnergyPrice('96a14971-525a-4420-aae9-e5aedaa129ff'); 85 | // expect(price).toBeDefined(); 86 | // }, 30000); 87 | 88 | // test('TibberQuery.getCurrentEnergyPrices() should be valid', async () => { 89 | // const prices = await tibberQuery.getCurrentEnergyPrices(); 90 | // expect(prices).toBeDefined(); 91 | // expect(prices.length).toBeGreaterThan(0); 92 | // prices.forEach((price) => { 93 | // expect(price.total).toBeGreaterThan(0); 94 | // }); 95 | // }, 30000); 96 | 97 | // test('TibberQuery.getCurrentEnergyPrices() should be valid', async () => { 98 | // const prices = await tibberQuery.getCurrentEnergyPrices(); 99 | // expect(prices).toBeDefined(); 100 | // expect(prices.length).toBeGreaterThan(0); 101 | // prices.forEach((price) => { 102 | // expect(price.total).toBeGreaterThan(0); 103 | // }); 104 | // }, 30000); 105 | 106 | // test('TibberQuery.getTodaysEnergyPrices() should be valid', async () => { 107 | // const prices = await tibberQuery.getTodaysEnergyPrices('96a14971-525a-4420-aae9-e5aedaa129ff'); 108 | // expect(prices).toBeDefined(); 109 | // expect(prices.length).toBeGreaterThan(0); 110 | // prices.forEach((price) => { 111 | // expect(price.total).toBeGreaterThan(0); 112 | // }); 113 | // }, 30000); 114 | 115 | // test('TibberQuery.getTomorrowsEnergyPrices() should be valid', async () => { 116 | // const prices = await tibberQuery.getTomorrowsEnergyPrices('96a14971-525a-4420-aae9-e5aedaa129ff'); 117 | // expect(prices).toBeDefined(); 118 | // if (prices.length) { 119 | // prices.forEach((price) => { 120 | // expect(price.total).toBeGreaterThan(0); 121 | // }); 122 | // } 123 | // }, 30000); 124 | 125 | // test('TibberQuery.sendPushNotification() should return error when using demo user', async () => { 126 | // const message = 'TEST_MESSAGE'; 127 | // const title = 'TEST_TITLE'; 128 | // const screenToOpen = AppScreen.HOME; 129 | 130 | // process.nextTick(() => { }); 131 | // const result: ISendPushNotification = await tibberQuery.sendPushNotification(title, message, screenToOpen); 132 | // if (result.errors != undefined) result.errors.map((m) => expect(m.message).toContain('operation not allowed for demo user')); 133 | // }, 30000); 134 | -------------------------------------------------------------------------------- /tests/test2-integration-tibber-feed.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import { TibberFeed, IConfig, TibberQuery } from '../src/index'; 3 | 4 | const config: IConfig = { 5 | active: true, 6 | apiEndpoint: { 7 | apiKey: '3A77EECF61BD445F47241A5A36202185C35AF3AF58609E19B53F3A8872AD7BE1-1', // Demo token 8 | queryUrl: 'https://api.tibber.com/v1-beta/gql', 9 | userAgent: 'test2-integration-tibber-feed', 10 | }, 11 | homeId: '96a14971-525a-4420-aae9-e5aedaa129ff', 12 | timestamp: true, 13 | power: true, 14 | currentL1: true, 15 | currentL2: true, 16 | currentL3: true, 17 | signalStrength: true, 18 | }; 19 | 20 | test('TibberFeed - Should be created', () => { 21 | expect(async () => { 22 | const query = new TibberQuery(config); 23 | const feed = new TibberFeed(query); 24 | return feed; 25 | }).toBeDefined(); 26 | }); 27 | 28 | /* 29 | test('TibberFeed -should be connected', done => { 30 | const query = new TibberQuery(config); 31 | const feed = new TibberFeed(query); 32 | feed.on(GQL.CONNECTION_ACK, (data: any) => { 33 | expect(data).toBeDefined(); 34 | feed.close(); 35 | done(); 36 | }); 37 | feed.connect(); 38 | }, 30000); 39 | */ 40 | 41 | /* 42 | test('TibberFeed - Should receive data', done => { 43 | const query = new TibberQuery(config); 44 | const feed = new TibberFeed(query); 45 | feed.on('data', data => { 46 | expect(data).toBeDefined(); 47 | feed.close(); 48 | done(); 49 | }); 50 | feed.on('error', error => { 51 | console.error(error); 52 | throw new Error(error); 53 | }); 54 | feed.connect(); 55 | }, 30000); 56 | */ 57 | -------------------------------------------------------------------------------- /tests/test3-tibber-api-endpoint.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import { UrlTools } from '../src/index'; 3 | 4 | const urlTools = new UrlTools(); 5 | test('Validate wss url should validate', () => { 6 | expect(urlTools.validateUrl('wss://api.tibber.com/v1-beta/gql/subscriptions')).toBe(true); 7 | }); 8 | test('Validate https url should validate', () => { 9 | expect(urlTools.validateUrl('https://api.tibber.com/v1-beta/gql')).toBe(true); 10 | }); 11 | test('Validate wss url should not validate', () => { 12 | expect(urlTools.validateUrl('wss//api.tibber.com/v1-beta/gql/subscriptions')).toBe(false); 13 | }); 14 | test('Validate https url should not validate', () => { 15 | expect(urlTools.validateUrl('https//api.tibber.com/v1-beta/gql')).toBe(false); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/test4-tibber-feed-1.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import * as url from 'url'; 3 | import { IConfig, TibberFeed } from '../src/index'; 4 | import { WebSocketServer } from 'ws'; 5 | import { GQL } from '../src/nodes/models/GQL'; 6 | import { TibberQueryBase } from '../src/nodes/TibberQueryBase'; 7 | 8 | let server: WebSocketServer; 9 | 10 | export class FakeTibberQuery extends TibberQueryBase { 11 | /** 12 | * Constructor 13 | * Create an instace of TibberQuery class 14 | * @param config IConfig object 15 | * @see IConfig 16 | */ 17 | constructor(config: IConfig) { 18 | super(config); 19 | } 20 | 21 | public override async getWebsocketSubscriptionUrl(): Promise { 22 | return new url.URL(this.config.apiEndpoint.queryUrl); 23 | } 24 | 25 | public override async getRealTimeEnabled(homeId: string): Promise { 26 | return true; 27 | } 28 | 29 | } 30 | 31 | beforeAll(async () => { 32 | server = new WebSocketServer({ port: 1337 }); 33 | server.on('connection', socket => { 34 | socket.on('message', (msg: string) => { 35 | let obj = JSON.parse(msg); 36 | if (obj.type === GQL.CONNECTION_INIT && obj.payload?.token === '1337') { 37 | obj.type = GQL.CONNECTION_ACK; 38 | socket.send(JSON.stringify(obj)); 39 | } else if (obj.type === GQL.SUBSCRIBE 40 | && obj.payload.query 41 | && obj.payload.query.startsWith('subscription($homeId:ID!){liveMeasurement(homeId:$homeId){') 42 | && obj.payload.variables 43 | && obj.payload.variables.homeId === '1337') { 44 | obj = { 45 | id: obj.id, 46 | payload: { data: { liveMeasurement: { value: 1337 } } }, 47 | type: GQL.NEXT, 48 | }; 49 | socket.send(JSON.stringify(obj)); 50 | } 51 | }); 52 | socket.on('close', () => { 53 | return; 54 | }); 55 | }); 56 | }); 57 | 58 | afterAll(async () => { 59 | if (server) { 60 | // Close all client sockets first 61 | for (const ws of server.clients) { 62 | ws.terminate(); // terminate is safer for test cleanup 63 | } 64 | // Wait for server to close 65 | await new Promise(resolve => server.close(() => resolve())); 66 | } 67 | }); 68 | 69 | const feeds: TibberFeed[] = []; 70 | 71 | afterEach(() => { 72 | for (const feed of feeds) { 73 | feed.active = false; 74 | feed.close(); 75 | feed.removeAllListeners(); 76 | } 77 | feeds.length = 0; 78 | }); 79 | 80 | test('TibberFeed - should be connected', done => { 81 | const query = new FakeTibberQuery({ 82 | active: true, 83 | apiEndpoint: { 84 | apiKey: '1337', 85 | queryUrl: 'ws://localhost:1337', 86 | userAgent: 'test4-tibber-feed', 87 | }, 88 | homeId: '1337', 89 | }); 90 | const feed = new TibberFeed(query, 30000, true, 5000); 91 | feeds.push(feed); 92 | feed.on(GQL.CONNECTION_ACK, (data: any) => { 93 | expect(data).toBeDefined(); 94 | expect(data.payload?.token).toBe('1337'); 95 | feed.active = false; 96 | done(); 97 | }); 98 | feed.on('heartbeat_timeout', data => { 99 | // console.log('heartbeat_timeout -> TibberFeed - should be connected'); 100 | }); 101 | feed.on('connection_timeout', data => { 102 | // console.log('connection_timeout -> TibberFeed - should be connected'); 103 | }); 104 | feed.connect(); 105 | }, 60000); 106 | 107 | test('TibberFeed - Should receive data', done => { 108 | (async done => { 109 | const query = new FakeTibberQuery({ 110 | active: true, 111 | apiEndpoint: { 112 | apiKey: '1337', 113 | queryUrl: 'ws://localhost:1337', 114 | userAgent: 'test4-tibber-feed', 115 | }, 116 | homeId: '1337', 117 | }); 118 | const feed = new TibberFeed(query); 119 | feeds.push(feed); 120 | feed.on('data', data => { 121 | expect(data).toBeDefined(); 122 | expect(data.value).toBe(1337); 123 | feed.close(); 124 | }); 125 | feed.on('disconnected', data => { 126 | feed.active = false; 127 | feed.close(); 128 | done(); 129 | }); 130 | feed.on('heartbeat_timeout', data => { 131 | // console.log('heartbeat_timeout -> TibberFeed - Should receive data'); 132 | }); 133 | await feed.connect(); 134 | })(done); 135 | }); 136 | 137 | test('TibberFeed - Should be active', () => { 138 | const query = new FakeTibberQuery({ 139 | active: true, 140 | apiEndpoint: { 141 | apiKey: '1337', 142 | queryUrl: 'ws://localhost:1337', 143 | userAgent: 'test4-tibber-feed', 144 | }, 145 | homeId: '1337', 146 | }); 147 | const feed = new TibberFeed(query); 148 | feeds.push(feed); 149 | feed.on('heartbeat_timeout', data => { 150 | // console.log('heartbeat_timeout -> TibberFeed - Should be active'); 151 | }); 152 | expect(feed.active).toBe(true); 153 | feed.active = false; 154 | feed.close(); 155 | }); 156 | 157 | test('TibberFeed - Should be inactive', () => { 158 | const query = new FakeTibberQuery({ active: false, apiEndpoint: { apiKey: '', queryUrl: '', userAgent: '' } }); 159 | const feed = new TibberFeed(query); 160 | feeds.push(feed); 161 | feed.on('heartbeat_timeout', data => { 162 | // console.log('heartbeat_timeout -> TibberFeed - should be inactive'); 163 | }); 164 | expect(feed.active).toBe(false); 165 | feed.active = false; 166 | feed.close(); 167 | }); 168 | 169 | test('TibberFeed - Should timeout after 3 sec', done => { 170 | const query = new FakeTibberQuery({ 171 | active: true, 172 | apiEndpoint: { 173 | apiKey: '1337', 174 | queryUrl: 'ws://localhost:1337', 175 | userAgent: 'test4-tibber-feed', 176 | }, 177 | homeId: '1337', 178 | }); 179 | const feed = new TibberFeed(query, 3000); 180 | feeds.push(feed); 181 | let called = false; 182 | feed.on(GQL.CONNECTION_ACK, data => { 183 | // feed.heartbeat(); 184 | }); 185 | feed.on('heartbeat_timeout', data => { 186 | // console.log('heartbeat_timeout -> TibberFeed - Should timeout after 3 sec'); 187 | }); 188 | feed.on('disconnected', data => { 189 | expect(data).toBeDefined(); 190 | if (!called) { 191 | called = true; 192 | feed.active = false; 193 | feed.close(); 194 | done(); 195 | } 196 | }); 197 | feed.connect(); 198 | }, 10000); 199 | 200 | test('TibberFeed - Should reconnect 3 times after 5 sec. timeout', done => { 201 | const query = new FakeTibberQuery({ 202 | active: true, 203 | apiEndpoint: { 204 | apiKey: '1337', 205 | queryUrl: 'ws://localhost:1337', 206 | userAgent: 'test4-tibber-feed', 207 | }, 208 | homeId: '1337', 209 | }); 210 | const feed = new TibberFeed(query, 5000); 211 | feeds.push(feed); 212 | const maxRetry = 3; 213 | let timeoutCount = 0; 214 | let reconnectCount = 0; 215 | feed.on(GQL.CONNECTION_ACK, data => { 216 | expect(data).toBeDefined(); 217 | expect(data.payload?.token).toBe('1337'); 218 | }); 219 | feed.on('heartbeat_timeout', data => { 220 | timeoutCount++; 221 | expect(data).toBeDefined(); 222 | // console.log('heartbeat_timeout -> TibberFeed - Should reconnect 3 times after 5 sec. timeout', Date.now().toLocaleString(), data); 223 | }); 224 | feed.on('heartbeat_reconnect', data => { 225 | reconnectCount++; 226 | expect(data).toBeDefined(); 227 | if (timeoutCount === maxRetry && reconnectCount == maxRetry) { 228 | feed.active = false; 229 | done(); 230 | } 231 | // console.log('heartbeat_reconnect -> TibberFeed - Should reconnect 3 times after 5 sec. timeout', Date.now().toLocaleString(), data); 232 | }); 233 | feed.connect(); 234 | }, 60000); 235 | -------------------------------------------------------------------------------- /tests/test4-tibber-feed-2.ts: -------------------------------------------------------------------------------- 1 | import nock from 'nock'; 2 | import { TibberFeed } from '../src'; 3 | import { FakeTibberQuery } from './test4-tibber-feed-1'; 4 | 5 | function createFakeWebSocket(): any { 6 | return { 7 | onopen: undefined, 8 | onmessage: undefined, 9 | onerror: undefined, 10 | onclose: undefined, 11 | send: jest.fn(), 12 | close: jest.fn() 13 | }; 14 | } 15 | 16 | describe('Testing TibberFeed ', () => { 17 | 18 | const feeds: TibberFeed[] = []; 19 | 20 | beforeAll(() => { }) 21 | afterAll(() => { 22 | nock.restore() 23 | }) 24 | beforeEach(() => { }) 25 | afterEach(() => { 26 | jest.clearAllTimers?.(); 27 | jest.clearAllMocks(); 28 | for (const feed of feeds) { 29 | feed.active = false; 30 | feed.close(); 31 | feed.removeAllListeners(); 32 | } 33 | feeds.length = 0; 34 | }); 35 | 36 | 37 | const mockQuery = new FakeTibberQuery({ 38 | active: true, 39 | apiEndpoint: { 40 | apiKey: '1337', 41 | queryUrl: 'ws://localhost:1337', 42 | userAgent: 'test4-tibber-feed', 43 | }, 44 | homeId: '1337', 45 | }); 46 | it('should throw if apiKey is missing', async () => { 47 | const queryMissingKey = new FakeTibberQuery({ 48 | active: true, 49 | apiEndpoint: { 50 | apiKey: '', 51 | queryUrl: 'ws://localhost:1337', 52 | userAgent: 'test4-tibber-feed', 53 | }, 54 | homeId: '1337', 55 | }); 56 | const connector = new TibberFeed(queryMissingKey, 30000, true, 5000); 57 | feeds.push(connector); 58 | 59 | await expect((connector as any).internalConnect()).rejects.toThrow('Missing mandatory parameters'); 60 | }); 61 | 62 | it('should call getWebsocketSubscriptionUrl()', async () => { 63 | const mockQuery = new FakeTibberQuery({ 64 | active: true, 65 | apiEndpoint: { 66 | apiKey: '1337', 67 | queryUrl: 'ws://localhost:1337', 68 | userAgent: 'test4-tibber-feed', 69 | }, 70 | homeId: '1337', 71 | }); 72 | jest.spyOn(mockQuery, 'getWebsocketSubscriptionUrl').mockResolvedValue(new URL('wss://example.com/')); 73 | 74 | const factory = jest.fn().mockReturnValue(createFakeWebSocket()); 75 | 76 | const connector = new TibberFeed(mockQuery, 30000, true, 5000); 77 | feeds.push(connector); 78 | connector['_webSocketFactory'] = factory; 79 | 80 | await (connector as any).internalConnect(); 81 | 82 | expect(mockQuery.getWebsocketSubscriptionUrl).toHaveBeenCalled(); 83 | expect(factory).toHaveBeenCalledWith( 84 | 'wss://example.com/', 85 | ['graphql-transport-ws'], 86 | expect.objectContaining({ headers: expect.any(Object) }) 87 | ); 88 | }); 89 | 90 | it('should attach WebSocket event handlers', async () => { 91 | const wsMock = createFakeWebSocket(); 92 | const factory = jest.fn().mockReturnValue(wsMock); 93 | 94 | const connector = new TibberFeed(mockQuery, 30000, true, 5000); 95 | feeds.push(connector); 96 | connector['_webSocketFactory'] = factory; 97 | 98 | const open = jest.fn(), msg = jest.fn(), err = jest.fn(), close = jest.fn(); 99 | 100 | // Use public event registration methods if available, e.g. 'on' or 'addEventListener' 101 | if (typeof (connector as any).on === 'function') { 102 | (connector as any).on('open', open); 103 | (connector as any).on('message', msg); 104 | (connector as any).on('error', err); 105 | (connector as any).on('close', close); 106 | } else { 107 | // fallback for test: skip handler assignment if not possible 108 | } 109 | 110 | await (connector as any).internalConnect(); 111 | 112 | expect(wsMock.onopen).toBeInstanceOf(Function); 113 | expect(wsMock.onmessage).toBeInstanceOf(Function); 114 | expect(wsMock.onerror).toBeInstanceOf(Function); 115 | expect(wsMock.onclose).toBeInstanceOf(Function); 116 | }); 117 | 118 | }); 119 | -------------------------------------------------------------------------------- /tests/test5-tibber-query.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import { TibberQuery } from '../src/index' 3 | import nock from 'nock' 4 | 5 | describe('Testing TibberQuery HTTP client', () => { 6 | 7 | beforeAll(() => { }) 8 | afterAll(() => { 9 | nock.restore() 10 | }) 11 | beforeEach(() => { }) 12 | afterEach(() => { 13 | jest.clearAllTimers(); 14 | }) 15 | const tibberResponseHomes = { "viewer": { "homes": [{ "id": "c70dcbe5-4485-4821-933d-a8a86452737b", "address": { "address1": "Kungsgatan 8", "address2": null, "address3": null, "postalCode": "11759", "city": "Stockholm", "country": "SE", "latitude": "59.3362066", "longitude": "18.0675126" } }] } } 16 | 17 | it('Should return a valid home', async () => { 18 | const scope = nock('https://api.tibber.com') 19 | .post(/.*/) // Match any POST path 20 | .reply(200, tibberResponseHomes, { 'Content-Type': 'application/json' }) 21 | 22 | const query = new TibberQuery({ apiEndpoint: { apiKey: '1337', queryUrl: 'https://api.tibber.com' }, active: false }) 23 | 24 | const res = await query.getHomes() 25 | scope.done() 26 | expect(res).toMatchObject(tibberResponseHomes.viewer.homes) 27 | 28 | }, 60000) 29 | 30 | it('Should not crash when server returns an error', async () => { 31 | const scope = nock('https://api.tibber.com') 32 | .post(/.*/) // Match any POST path 33 | .reply(502, 'Bad Gateway') 34 | 35 | // Ensure nock is enabled and intercepting 36 | expect(nock.isActive()).toBe(true); 37 | 38 | const query = new TibberQuery({ 39 | apiEndpoint: { apiKey: '1337', queryUrl: 'https://api.tibber.com' }, 40 | active: false, 41 | }); 42 | 43 | await expect(query.getHomes()).rejects.toMatchObject({ 44 | httpCode: 502, 45 | responseMessage: 'Bad Gateway', 46 | statusCode: 502, 47 | statusMessage: 'Bad Gateway' 48 | }); 49 | 50 | scope.done(); 51 | }, 60000) 52 | }) 53 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./lib", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | /* Strict Type-Checking Options */ 25 | "strict": true, /* Enable all strict type-checking options. */ 26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | /* Module Resolution Options */ 39 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 46 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 49 | /* Source Map Options */ 50 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | /* Experimental Options */ 55 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 56 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 57 | "resolveJsonModule": true 58 | }, 59 | "include": [ 60 | "src", 61 | "examples" 62 | ], 63 | "exclude": [ 64 | "node_modules", 65 | "**/tests/*" 66 | ] 67 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-config-prettier" 5 | ], 6 | "rules": { 7 | "ordered-imports": false, 8 | "no-console": false, 9 | "object-literal-sort-keys": false, 10 | "variable-name": { 11 | "options": [ 12 | "ban-keywords", 13 | "check-format", 14 | "allow-leading-underscore" 15 | ] 16 | } 17 | } 18 | } --------------------------------------------------------------------------------