├── package.json ├── LICENSE ├── README.md ├── .gitignore └── index.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dexcom", 3 | "version": "2.0.0", 4 | "description": "A simple javascript library for reading glucose data from the Dexcom Share API.", 5 | "main": "index.js", 6 | "author": "Carter Kearns (https://github.com/coderkearns)", 7 | "license": "MIT", 8 | "dependencies": {} 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 coderkearns 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 | # dexcom 2 | 3 | A simple non-official javascript library for reading glucose data from the Dexcom Share API. 4 | ## Installation 5 | 6 | ### Browser 7 | 8 | dexcom can be used client-side (in-browser) by adding the script to your html: 9 | ```shell 10 | $ curl -sL https://raw.githubusercontent.com/coderkearns/dexcom/master/index.js > dexcom.js 11 | ``` 12 | ```html 13 | 14 | ``` 15 | 16 | ### Node.js 17 | 18 | dexcom can also be used in node.js, but requires fetch. It can currently be used in Node.js v17.6.x and above. 19 | 20 | ```shell 21 | $ curl -sL https://raw.githubusercontent.com/coderkearns/dexcom/master/index.js > dexcom.js 22 | $ node --version 23 | v17.6.0 24 | $ node --experimental-fetch .js 25 | ``` 26 | ```js 27 | // .js 28 | const Client = require('./dexcom.js'); 29 | ``` 30 | 31 | ## Usage 32 | 33 | ### Step 1. Enable the Dexcom Share service on your account 34 | 35 | *This step only needs to be done once per account.* 36 | 37 | Download the [Dexcom G6 / G5 / G4](https://www.dexcom.com/apps) app, then [enable the Share service](https://provider.dexcom.com/education-research/cgm-education-use/videos/setting-dexcom-share-and-follow). 38 | 39 | ### Step 2. Create a Client instance 40 | 41 | ```js 42 | const dexcomClient = new Client() 43 | ``` 44 | 45 | ### Step 3. Log in to an account 46 | 47 | ```js 48 | await dexcomClient.login("myUsername", "myPasswordIsCool1*") 49 | ``` 50 | 51 | ### Step 4. Get your glucose values 52 | 53 | ```js 54 | const lastReading = await dexcomClient.fetchLastReading() 55 | 56 | console.log(`Reading value is ${lastReading.trend.arrow}${lastReading.mgdl}. The value is ${lastReading.trend.desc}. The value was measured at ${lastReading.time}.`) 57 | 58 | const last20ReadingsInTheLast1Hour = await dexcomClient.fetchReadings(60, 20) 59 | ``` 60 | 61 | ## License 62 | 63 | This project is licensed under the [MIT](https://choosealicense.com/licenses/mit/) License - see the [LICENSE](./LICENSE) file for details. 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | 3 | # temporary files which can be created if a process still has a handle open of a deleted file 4 | .fuse_hidden* 5 | 6 | # KDE directory preferences 7 | .directory 8 | 9 | # Linux trash folder which might appear on any partition or disk 10 | .Trash-* 11 | 12 | # .nfs files are created when an open file is removed but is still being accessed 13 | .nfs* 14 | # Logs 15 | logs 16 | *.log 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | lerna-debug.log* 21 | .pnpm-debug.log* 22 | 23 | # Diagnostic reports (https://nodejs.org/api/report.html) 24 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 25 | 26 | # Runtime data 27 | pids 28 | *.pid 29 | *.seed 30 | *.pid.lock 31 | 32 | # Directory for instrumented libs generated by jscoverage/JSCover 33 | lib-cov 34 | 35 | # Coverage directory used by tools like istanbul 36 | coverage 37 | *.lcov 38 | 39 | # nyc test coverage 40 | .nyc_output 41 | 42 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | bower_components 47 | 48 | # node-waf configuration 49 | .lock-wscript 50 | 51 | # Compiled binary addons (https://nodejs.org/api/addons.html) 52 | build/Release 53 | 54 | # Dependency directories 55 | node_modules/ 56 | jspm_packages/ 57 | 58 | # Snowpack dependency directory (https://snowpack.dev/) 59 | web_modules/ 60 | 61 | # TypeScript cache 62 | *.tsbuildinfo 63 | 64 | # Optional npm cache directory 65 | .npm 66 | 67 | # Optional eslint cache 68 | .eslintcache 69 | 70 | # Optional stylelint cache 71 | .stylelintcache 72 | 73 | # Microbundle cache 74 | .rpt2_cache/ 75 | .rts2_cache_cjs/ 76 | .rts2_cache_es/ 77 | .rts2_cache_umd/ 78 | 79 | # Optional REPL history 80 | .node_repl_history 81 | 82 | # Output of 'npm pack' 83 | *.tgz 84 | 85 | # Yarn Integrity file 86 | .yarn-integrity 87 | 88 | # dotenv environment variables file 89 | .env.development.local 90 | .env.test.local 91 | .env.production.local 92 | .env.local 93 | 94 | # parcel-bundler cache (https://parceljs.org/) 95 | .cache 96 | .parcel-cache 97 | 98 | # Next.js build output 99 | .next 100 | out 101 | 102 | # Nuxt.js build / generate output 103 | .nuxt 104 | dist 105 | 106 | # Gatsby files 107 | .cache/ 108 | # Comment in the public line in if your project uses Gatsby and not Next.js 109 | # https://nextjs.org/blog/next-9-1#public-directory-support 110 | # public 111 | 112 | # vuepress build output 113 | .vuepress/dist 114 | 115 | # vuepress v2.x temp and cache directory 116 | .temp 117 | .cache 118 | 119 | # Serverless directories 120 | .serverless/ 121 | 122 | # FuseBox cache 123 | .fusebox/ 124 | 125 | # DynamoDB Local files 126 | .dynamodb/ 127 | 128 | # TernJS port file 129 | .tern-port 130 | 131 | # Stores VSCode versions used for testing VSCode extensions 132 | .vscode-test 133 | 134 | # yarn v2 135 | .yarn/cache 136 | .yarn/unplugged 137 | .yarn/build-state.yml 138 | .yarn/install-state.gz 139 | .pnp.* 140 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Client = (function () { 2 | /****** GLOBAL VARIABLES ******/ 3 | const APP_ID = "d89443d2-327c-4a6f-89e5-496bbb0317db" 4 | 5 | const URL_BASE = "https://share2.dexcom.com/ShareWebServices/Services" 6 | const URL_BASE_OUS = 7 | "https://shareous1.dexcom.com/ShareWebServices/Services" 8 | 9 | const ENDPOINT_LOGIN = "/General/LoginPublisherAccountById" 10 | const ENDPOINT_AUTHENTICATE = "/General/AuthenticatePublisherAccount" 11 | const ENDPOINT_GLUCOSE_READINGS = 12 | "/Publisher/ReadPublisherLatestGlucoseValues" 13 | 14 | const DEFAULT_SESSION_ID = "00000000-0000-0000-0000-000000000000" 15 | 16 | const TREND_DESCRIPTIONS = { 17 | None: { name: "None", desc: "", arrow: "" }, 18 | DoubleUp: { name: "DoubleUp", desc: "rising quickly", arrow: "\u2B85" }, 19 | SingleUp: { name: "SingleUp", desc: "rising", arrow: "\u2191" }, 20 | FortyFiveUp: { 21 | name: "FortyFiveUp", 22 | desc: "rising slightly", 23 | arrow: "\u2197", 24 | }, 25 | Flat: { name: "Flat", desc: "steady", arrow: "\u2192" }, 26 | FortyFiveDown: { 27 | name: "FortyFiveDown", 28 | desc: "falling slightly", 29 | arrow: "\u2198", 30 | }, 31 | SingleDown: { name: "SingleDown", desc: "falling", arrow: "\u2193" }, 32 | DoubleDown: { 33 | name: "DoubleDown", 34 | desc: "falling quickly", 35 | arrow: "\u2B87", 36 | }, 37 | NotComputable: { 38 | name: "NotComputable", 39 | desc: "unable to determine trend", 40 | arrow: "?", 41 | }, 42 | RateOutOfRange: { 43 | name: "RateOutOfRange", 44 | desc: "trend unavailable", 45 | arrow: "-", 46 | }, 47 | } 48 | 49 | const MMOLL_TO_MGDL_CONVERTION_FACTOR = 0.0555 // (mmol/L) = (mg/dl) * 0.0555 50 | 51 | class Client { 52 | constructor(OutOfUS = false) { 53 | this._baseUrl = OutOfUS ? URL_BASE_OUS : URL_BASE 54 | } 55 | 56 | _request(endpoint, data) { 57 | return fetch(this._baseUrl + endpoint, { 58 | method: "POST", 59 | headers: { 60 | "Content-Type": "application/json", 61 | Accept: "application/json", 62 | }, 63 | body: JSON.stringify(data), 64 | }).then(response => response.json()) 65 | } 66 | 67 | _authenticate(username, password) { 68 | return this._request(ENDPOINT_AUTHENTICATE, { 69 | accountName: username, 70 | password, 71 | applicationId: APP_ID, 72 | }) 73 | } 74 | _login(accountId, password) { 75 | return this._request(ENDPOINT_LOGIN, { 76 | accountId, 77 | password, 78 | applicationId: APP_ID, 79 | }) 80 | } 81 | 82 | /** 83 | * Creates a Dexcom session using the given Dexcom Share username and password. 84 | * @param {string} username 85 | * @param {string} password 86 | * @returns {Promise} Promise that resolves on login success 87 | * @throws Error if login fails 88 | */ 89 | async login(username, password) { 90 | try { 91 | const accountId = await this._authenticate(username, password) 92 | this.sessionId = await this._login(accountId, password) 93 | } catch (e) { 94 | throw new Error("Error creating session") 95 | } 96 | 97 | if (this.sessionId == DEFAULT_SESSION_ID) 98 | throw new Error("Invalid session") 99 | } 100 | 101 | /** 102 | * Fetches multiple glucose readings from the Dexcom Share service. Requires login() first. 103 | * @param {number} maxAge The maximum age of readings to fetch, in minutes 104 | * @param {*} maxCount The maximum number of readings to fetch 105 | * @returns Promise that resolves with an array of glucose readings: `{ trend: { name: string, desc: string, arrow: string }, mgdl: number, mmol: number, time: Date }` 106 | * @throws Error if not logged in, arguments are invalid, or fetch fails 107 | */ 108 | async fetchReadings(maxAge = 1440, maxCount = 288) { 109 | if (!this.sessionId) throw new Error("Not yet logged in") 110 | 111 | if (maxAge < 1 || maxAge > 1440) 112 | throw new Error("Minutes must be between 1 and 1440") 113 | if (maxCount < 1 || maxCount > 288) 114 | throw new Error("Max count must be between 1 and 288") 115 | 116 | try { 117 | const readings = await this._request( 118 | ENDPOINT_GLUCOSE_READINGS, 119 | { 120 | sessionId: this.sessionId, 121 | minutes: maxAge, 122 | maxCount, 123 | } 124 | ) 125 | return readings.map(reading => this._processReadings(reading)) 126 | } catch (e) { 127 | console.error(e) 128 | throw new Error("Error fetching glucose readings") 129 | } 130 | } 131 | 132 | _processReadings(reading) { 133 | return { 134 | trend: TREND_DESCRIPTIONS[reading.Trend], 135 | mgdl: reading.Value, 136 | mmol: 137 | Math.round( 138 | reading.Value * MMOLL_TO_MGDL_CONVERTION_FACTOR * 10 139 | ) / 10, 140 | time: new Date( 141 | parseInt(reading.WT.replace("Date(", "").replace(")", "")) 142 | ), 143 | } 144 | } 145 | 146 | /** 147 | * Fetches the most recent glucose reading within the last day. Requires login() first. 148 | * @returns {Promise} Promise that resolves with the most recent glucose reading, or undefined if no readings are available 149 | * @throws Error if not logged in 150 | */ 151 | fetchLastReading() { 152 | return this.fetchReadings(1440, 1).then(readings => readings[0]) 153 | } 154 | } 155 | 156 | return Client 157 | })() 158 | 159 | // Node.js support 160 | if (typeof module !== "undefined" && typeof module.exports !== "undefined") { 161 | module.exports = Client 162 | } 163 | --------------------------------------------------------------------------------