diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..0fe3f6de0 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +EDU_EMAIL=SEE_README +EDU_PW=SEE_README +EDU_API_URL=SEE_README \ No newline at end of file diff --git a/.gitignore b/.gitignore index 136293a84..7fb431e21 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # Misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/docs/network-iot/learn/EduDevice.tsx b/docs/network-iot/learn/EduDevice.tsx new file mode 100644 index 000000000..d4321af85 --- /dev/null +++ b/docs/network-iot/learn/EduDevice.tsx @@ -0,0 +1,423 @@ +import { ApplicationServiceClient } from '@chirpstack/chirpstack-api-grpc-web/api/application_grpc_web_pb' +import { ListApplicationsRequest } from '@chirpstack/chirpstack-api-grpc-web/api/application_pb' +import { DeviceServiceClient } from '@chirpstack/chirpstack-api-grpc-web/api/device_grpc_web_pb' +import { + CreateDeviceKeysRequest, + CreateDeviceRequest, + Device, + DeviceKeys, +} from '@chirpstack/chirpstack-api-grpc-web/api/device_pb' +import { DeviceProfileServiceClient } from '@chirpstack/chirpstack-api-grpc-web/api/device_profile_grpc_web_pb' +import { ListDeviceProfilesRequest } from '@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb' +import { InternalServiceClient } from '@chirpstack/chirpstack-api-grpc-web/api/internal_grpc_web_pb' +import { + LogItem, + LoginRequest, + StreamDeviceEventsRequest, +} from '@chirpstack/chirpstack-api-grpc-web/api/internal_pb' +import { Region } from '@chirpstack/chirpstack-api-grpc-web/common/common_pb' +import useDocusaurusContext from '@docusaurus/useDocusaurusContext' +import { EventEmitter } from 'events' +import google_protobuf_empty_pb from 'google-protobuf/google/protobuf/empty_pb' +import * as grpcWeb from 'grpc-web' +import React, { useCallback, useEffect, useState } from 'react' +import { Alert, AlertIcon } from '../../../src/theme/components/Alert' +import styles from './build-a-device.module.css' + +const deviceEmitter = new EventEmitter() + +const generateRandomKey = () => { + let cryptoObj = window.crypto || window.Crypto + let b = new Uint8Array(8) + cryptoObj.getRandomValues(b) + let hexString = '' + b.forEach((num, index) => { + let hexVal = num.toString(16) + if (hexVal.length === 1) hexVal = '0' + hexVal + hexString += hexVal + }) + + return hexString +} + +const getHexArray = (key: string = '') => { + return key + .match(/[A-Fa-f0-9]{2}/g)! + .join(', ') + .toUpperCase() + .replace(/[A-Fa-f0-9]{2}/g, '0x$&') +} + +const DeviceInfoRow = ({ label, value }) => { + if (!value) + return ( +

+ + +

+ ) + return ( +

+ + e.target.select()} /> +

+ ) +} + +const useLogin = () => { + const { + siteConfig: { customFields }, + } = useDocusaurusContext() + const interalServiceClient = new InternalServiceClient(customFields.EDU_API_URL) + + return () => + new Promise((resolve, reject) => { + let req = new LoginRequest() + req.setEmail(customFields.EDU_EMAIL) + req.setPassword(customFields.EDU_PW) + interalServiceClient.login(req, {}, (error, resp) => { + if (error !== null) { + return reject(error) + } + return resolve(resp.getJwt()) + }) + }) +} + +const getRequestMeta = (jwt: string) => { + return { authorization: `Bearer ${jwt}` } +} + +const EnvChecker = ({ children }) => { + const { + siteConfig: { customFields }, + } = useDocusaurusContext() + if (!customFields.EDU_API_URL || !customFields.EDU_EMAIL || !customFields.EDU_PW) { + return ( + + +

Please configure your .env file to use the Educational LNS features

+
+ ) + } + + return children +} + +const CreateDeviceComponent = () => { + const { + siteConfig: { customFields }, + } = useDocusaurusContext() + console.log('url', customFields.EDU_API_URL) + const login = useLogin() + const [deviceInfo, setDeviceInfo] = useState({ devEui: '', joinEui: '', networkKey: '' }) + const [isLoading, setIsLoading] = useState(false) + + const createDevice = () => { + setIsLoading(true) + let jwt = '' + let tenants = [] + let applicationId = '' + let deviceProfileId = '' + const interalServiceClient = new InternalServiceClient(customFields.EDU_API_URL) + + const getTenantId = () => { + return tenants[0].getTenantId() + } + const loginWrapper = () => + login().then((loginJwt) => { + jwt = loginJwt as string + }) + + const getTenants = () => { + return new Promise((resolve, reject) => { + interalServiceClient.profile( + new google_protobuf_empty_pb.Empty(), + getRequestMeta(jwt), + (error, resp) => { + if (error !== null) { + reject(error) + } + tenants = resp.getTenantsList() + resolve(tenants) + }, + ) + }) + } + + const getDeviceApplications = () => + new Promise((resolve, reject) => { + const client = new ApplicationServiceClient(customFields.EDU_API_URL) + let req = new ListApplicationsRequest() + req.setTenantId(getTenantId()) + req.setLimit(10) + req.setOffset(0) + + client.list(req, getRequestMeta(jwt), (error, resp) => { + if (error !== null) { + return reject(error) + } + const deviceApplications = resp.toObject() + const targetApplication = deviceApplications.resultList.find( + (application) => application.name === 'Docs', + ) + if (!targetApplication) return reject('Target application not found') + applicationId = targetApplication.id + return resolve(applicationId) + }) + }) + + const getDeviceProfilesPage = () => + new Promise((resolve, reject) => { + const client = new DeviceProfileServiceClient(customFields.EDU_API_URL) + + let req = new ListDeviceProfilesRequest() + req.setTenantId(getTenantId()) + req.setLimit(10) + req.setOffset(0) + + client.list(req, getRequestMeta(jwt), (error, resp) => { + if (error !== null) { + return reject(error) + } + + const deviceProfiles = resp.toObject() + const targetProfile = deviceProfiles.resultList.find( + (deviceProfile) => deviceProfile.region === Region.US915, + ) + if (!targetProfile) return reject('Target profile not found') + + deviceProfileId = targetProfile.id + return resolve(deviceProfileId) + }) + }) + + const createDevice = () => + new Promise((resolve, reject) => { + const client = new DeviceServiceClient(customFields.EDU_API_URL) + let device = new Device() + + const devEui = generateRandomKey() + const joinEui = generateRandomKey() + + device.setApplicationId(applicationId) + device.setName(devEui) + device.setDescription('Randomly generated device') + device.setDevEui(devEui) + device.setDeviceProfileId(deviceProfileId) + device.setIsDisabled(false) + device.setSkipFcntCheck(false) + device.setJoinEui(joinEui) + + let req = new CreateDeviceRequest() + req.setDevice(device) + + client.create(req, getRequestMeta(jwt), (error) => { + if (error !== null) { + setIsLoading(false) + return reject(error) + } + + let req = new CreateDeviceKeysRequest() + let deviceKeys = new DeviceKeys() + const networkKey = generateRandomKey() + generateRandomKey() + deviceKeys.setDevEui(devEui) + deviceKeys.setNwkKey(networkKey) + req.setDeviceKeys(deviceKeys) + + client.createKeys(req, getRequestMeta(jwt), (error) => { + setIsLoading(false) + if (error !== null) { + return reject(error) + } + + setDeviceInfo({ + devEui, + joinEui, + networkKey, + }) + deviceEmitter.emit('deviceInfo', { devEui: devEui, jwt }) + + return resolve(device) + }) + }) + }) + + return loginWrapper() + .then(getTenants) + .then(() => Promise.all([getDeviceApplications(), getDeviceProfilesPage()])) + .then(createDevice) + } + + return ( + <> +
+ + + +
+ + + ) +} + +export const CreateDevice = () => ( + + + +) + +function DeviceEventLoggerComponent() { + const { + siteConfig: { customFields }, + } = useDocusaurusContext() + const login = useLogin() + const [jwt, setJwt] = useState('') + const [devEui, setDevEui] = useState('') + const [events, setEvents] = useState([]) + + useEffect(() => { + const setStreamInfo = ({ devEui, jwt }) => { + setJwt(jwt) + setDevEui(devEui) + } + deviceEmitter.on('deviceInfo', setStreamInfo) + return () => deviceEmitter.removeListener('deviceInfo', setStreamInfo) + }, []) + + const onMessage = useCallback((logItem: LogItem) => { + setEvents((e) => { + if ( + e.length === 0 || + parseInt(logItem.getId().replace('-', '')) > parseInt(e[0].id.replace('-', '')) + ) { + return [logItem.toObject(), ...e] + } + return e + }) + }, []) + + // get jwt when manual devEui entry + useEffect(() => { + if (devEui.length === 16 && !jwt) { + login().then(setJwt) + } + }, [jwt, devEui]) + + useEffect(() => { + if (devEui.length === 16 && !!jwt) { + const streamDeviceEvents = (callbackFunc: (resp: LogItem) => void): (() => void) => { + let req = new StreamDeviceEventsRequest() + req.setDevEui(devEui) + var stream: grpcWeb.ClientReadableStream | undefined = undefined + let retryCount = 0 + + let setup = () => { + const client = new InternalServiceClient(customFields.EDU_API_URL) + stream = client.streamDeviceEvents(req, getRequestMeta(jwt)) + + stream = stream.on('data', (resp) => { + callbackFunc(resp) + retryCount = 0 // reset retry count on successful data reception + }) + + stream = stream.on('end', function () { + retryCount++ + setTimeout(setup, Math.min(1000 * 2 ** retryCount, 30000)) // exponential backoff with max delay + }) + + stream = stream.on('error', function () { + retryCount++ + setTimeout(setup, Math.min(1000 * 2 ** retryCount, 30000)) // exponential backoff with max delay + }) + } + + setup() + + return () => { + if (stream) { + stream.cancel() + } + } + } + + let cancelFunc = streamDeviceEvents(onMessage) + + return () => { + cancelFunc() + } + } + }, [jwt, devEui, onMessage]) + + return ( + <> + {/*

Device Event Logger

*/} + {/*

jwt: {jwt}

*/} + + {!events.length && ( +
+ setDevEui(event.target.value)} + /> +

No events yet.

+
+ )} + +
+ {events + .filter((event) => event.description !== 'log') + .map((event) => ( +
+
+

+ {event.description === 'join' + ? '🔄 Joining' + : event.description === 'up' + ? '⬆️ Uplink' + : event.description === 'down' + ? '⬇️ Downlink' + : event.description === 'status' + ? '*️⃣ Status' + : `🛜 ${event.description}`} +

+

+ {new Date(event.time?.seconds! * 1000).toLocaleString()} +

+
+
    + {event.propertiesMap.map(([key, value], index) => ( +
  • + + {key === 'DR' + ? 'Data Rate' + : key === 'FCnt' + ? 'Frame Count' + : key === 'FPort' + ? 'Frame Port' + : key} + :{' '} + + {value} +
  • + ))} +
+
+ ))} +
+ + ) +} + +export const DeviceEventLogger = () => ( + + + +) diff --git a/docs/network-iot/learn/build-a-device.mdx b/docs/network-iot/learn/build-a-device.mdx new file mode 100644 index 000000000..86cd616b1 --- /dev/null +++ b/docs/network-iot/learn/build-a-device.mdx @@ -0,0 +1,90 @@ +--- +id: build-a-device +title: Build A LoRaWAN Device +pagination_label: Build A LoRaWAN Device +sidebar_label: Build A LoRaWAN Device +description: Helium Documentation +image: https://docs.helium.com/img/link-image.png +slug: /network-iot/learn/build-a-device +--- + +import useBaseUrl from '@docusaurus/useBaseUrl' +import { DocslabCodeBlock } from 'docslab-docusaurus/lib/theme/DocslabCodeBlock' +import { CreateDevice, DeviceEventLogger } from './EduDevice.tsx' + +
+ Notes: // Create a Device // Using an off-the-shelf development board. // distinction for + ready-to-use devices with pre-provisioned keys // Send data to the Helium Network // View the data + in the Console +
+ +Activating a commercially-produced device is often as simple as scanning a QR code or pairing with +an app. However, knowing the fundamentals of how LoRaWAN devices are onboarded, programmed, and +their data delivered helps to unlock the ideas and opportunities that come with these devices. +Therefore, this guide details the less common, but important principles of creating a custom-built +device on the Helium Network. + +## Provisioning a Device + +Many devices are pre-provisioned with keys. However, for this demonstration, we will create a new +set of keys for a device. The keys are used to authenticate the device on the network and encrypt +data. The keys are generated randomly in order to be unique to each device. + +Generate a set of keys below by clicking the "Create Device" button: + +
+ +
+
+ +Behind the scenes, these keys have been submitted to a LoRaWAN Network Server (LNS) which also +shares the Join EUI with the Helium Packet Router (HPR). With the Join EUI, HPR can receive payloads +from this device through any Hotspot in the world and route it to the appropriate LNS. Because these +keys are unique to the device and LNS, only the device and the LNS can decrypt the data transferred +between them. + +## Building a Device + +Consider the following code from +[the helium/longfi-platformio repository](https://github.com/helium/longfi-platformio/blob/master/Heltec-CubeCell-Board/examples/cubecell-helium-us915-basic/src/main.cpp): + +Copy the keys generated above into the `devEui`, `appEui`, and `appKey` variables in the code below: + +
+ +
+
+ +Some more text over here maybe + +
+ +
+
+ +If uplinks are shown in the logger above, the device is successfully sending data to the Helium +Network! + +With the payloads received, the next step would be to send them to an application. LNSs typically +support several integrations like HTTP, MQTT, or bespoke integrations with platforms like AWS IoT or +Azure IoT. + +## Practice + +Now that data is being sent to the Helium Network, experiment with the device. + +1. Try changing the data being sent by the device. How would you send the payload, "Hello, World!"? + The current payload is being sent in hexadecimal format. Tools like + [asciitohex.com](https://www.asciitohex.com/) can help convert data types. +1. LoRaWAN provides the 'port' field which helps separate data types. For instance, multiple sensors + can run on the same device under different ports or status updates like battery level can be sent + on a different port than sensor data. +1. The device is currently sending data every 30 seconds. Try changing the interval to 10 seconds. + How would this affect the battery life of the device? diff --git a/docs/network-iot/learn/build-a-device.module.css b/docs/network-iot/learn/build-a-device.module.css new file mode 100644 index 000000000..f1d1ed3ac --- /dev/null +++ b/docs/network-iot/learn/build-a-device.module.css @@ -0,0 +1,119 @@ +/*CreateDevice*/ +.DeviceKeys { + width: 100%; + padding: 0.5em; +} + +.keybox { + display: flex; + flex-direction: column; +} + +.keybox label { + font-weight: 500; + color: #53627c; + font-size: small; + background-color: #eaeff9; + line-height: 1em; + padding: 1em 1rem calc(1em + 6px); + border-radius: 8px 8px 0 0; +} + +.keybox input { + display: table; + background: white; + width: 100%; + table-layout: fixed; + border: none; + border-radius: 0 0 8px 8px; + padding: 1rem; + margin-top: -8px; + box-shadow: 0 3px 8px -3px rgba(0, 0, 0, 0.08); + font-size: 1rem; +} + +.keybox input:focus { + outline-color: rgb(47, 98, 192); +} + +.keybox input::placeholder { + color: #aaadb4; +} + +.keybox input:focus:placeholder { + outline: none; +} + +.createDeviceButton { + appearance: none; + cursor: pointer; + background-color: #fff; + border: none; + border-radius: 6px; + box-shadow: 0 3px 6px -3px #0000001a; + font-size: 1em; + margin-bottom: 1em; + padding: 0.5em; +} + +.createDeviceButton:disabled { + background-color: #e8e8e8; +} + +/*DeviceEventLogger*/ +.eventsContainer { + width: 100%; + max-height: 50vh; + overflow: scroll; + display: flex; + flex-direction: column; + padding: 2rem 1rem 1rem; + margin: -0.5rem; + scroll-snap-type: y proximity; +} + +.eventFrame { + background: white; + box-shadow: 0 3px 8px -3px rgba(0, 0, 0, 0.08); + border-radius: 0.5em; + overflow: scroll; + margin: 0 0.5rem 1rem; + flex: 0 0 auto; + scroll-snap-align: start; + scroll-snap-margin: 1rem; +} + +.eventHeader { + display: flex; + justify-content: space-between; + padding: 0.5em 1rem; + background: #eaeff9; +} + +.eventTime, +.eventType { + color: #53627c; + font-size: small; + padding: 0; + margin: 0; +} + +.eventType { + font-weight: 500; +} + +.eventTime { + color: #6a748c; +} + +.eventProperties { + list-style: none; + padding: 0.5rem 0 0.55rem; + margin: 0; + /* display: inline-flex; */ +} + +.eventProperties li { + margin-top: 0 !important; + padding: 0 1rem; +} diff --git a/docs/network-iot/learn/understanding-lorawan.mdx b/docs/network-iot/learn/understanding-lorawan.mdx new file mode 100644 index 000000000..ca4a70935 --- /dev/null +++ b/docs/network-iot/learn/understanding-lorawan.mdx @@ -0,0 +1,45 @@ +--- +id: understanding-lorawan +title: Understanding LoRaWAN +pagination_label: Understanding LoRaWAN +sidebar_label: Understanding LoRaWAN +description: Helium Documentation +image: https://docs.helium.com/img/link-image.png +slug: /network-iot/learn/understanding-lorawan +--- + + +LoRaWAN is a wireless communication protocol designed for long-range, low-power, and low-bandwidth applications. It is a critical technology for IoT (Internet of Things) networks, enabling devices to communicate over large distances with minimal energy consumption. + +```mermaid +graph TB; + A["LoRaWAN"] + + subgraph LR0["Long-Range"] + B1(Supports distances over several kilometers) + B2(Suitable for urban and rural areas) + end + + subgraph LB["Low-Bandwidth"] + C1(Optimized for small data transfers) + C2(Examples include sensor readings and status updates) + end + + subgraph LP["Low-Power"] + D1(Designed for battery-operated devices) + D2(Extends operational life up to several years) + end + + A --> LR0 + A --> LB + A --> LP +``` +The primary properties of LoRaWAN encompass long-range capabilities, allowing data transmission over several kilometers, making it suitable for both urban and rural deployments. It also supports low-power operations, designed for battery-operated devices, thus extending their operational life up to several years without the need for frequent recharging. Additionally, LoRaWAN is optimized for low-bandwidth applications that require small amounts of data transfer, such as sensor readings and status updates. + +LoRaWAN is versatile and can be used in various applications. In smart cities, it enables connectivity for street lighting, waste management, and parking solutions. In agriculture, it facilitates soil moisture monitoring, livestock tracking, and irrigation control. Industrial IoT benefits from LoRaWAN through predictive maintenance, asset tracking, and environmental monitoring in factories. Smart homes also utilize LoRaWAN for efficient control of home automation devices and security systems. + +Understanding the background of LoRaWAN involves several key components, including keys and security measures to ensure secure communications by using unique keys for device authentication and data encryption. The onboarding process for devices requires registering and commissioning end devices onto the network, which may involve configuring parameters and device keys. + +Provisioning a device through a LoRaWAN Network Server (LNS) is essential. The LNS manages the network by handling tasks such as device authentication, validating each device's identity to allow secure network access. It also manages data routing, directing data traffic between end devices and application servers, and optimizes network performance by handling join requests and maintaining device connectivity. + +Helium Hotspots play a fundamental role in providing coverage for LoRaWAN devices. Hotspots act as gateways that bridge the communication between end nodes (LoRaWAN devices) and the internet. By deploying Helium Hotspots, individuals and businesses contribute to a decentralized network, expanding the coverage area and enabling more devices to connect and communicate reliably. The relationship between gateways (Helium Hotspots) and end nodes (LoRaWAN devices) is that gateways transmit and receive data packets from multiple end nodes, providing the infrastructure necessary for widespread IoT applications. diff --git a/docusaurus.config.js b/docusaurus.config.js index 4550317c4..4045bdb1c 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -2,6 +2,8 @@ import { themes } from 'prism-react-renderer' import katex from 'rehype-katex' import math from 'remark-math' import webpack from 'webpack' +require('dotenv').config() + const darkCodeTheme = themes.dracula const lightCodeTheme = themes.github @@ -96,6 +98,9 @@ module.exports = { MIGRATION_SERVICE_URL: 'https://migration.web.helium.io', SOLANA_URL: 'https://solana-rpc.web.helium.io/?session-key=Pluto', TOKENS_TO_RENT_SERVICE_URL: 'https://tokens-to-rent.web.helium.io', + EDU_EMAIL: process.env.EDU_EMAIL, + EDU_PW: process.env.EDU_PW, + EDU_API_URL: process.env.EDU_API_URL, }, plugins: [ function (context, options) { diff --git a/package.json b/package.json index 57a48c283..cccfc5957 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "serve": "docusaurus serve" }, "dependencies": { + "@chirpstack/chirpstack-api-grpc-web": "^4.8.1", "@clockwork-xyz/sdk": "^0.3.0", "@coral-xyz/anchor": "^0.27.0", "@docusaurus/core": "^3.7.0", @@ -64,5 +65,8 @@ }, "engines": { "node": ">=18.0" + }, + "devDependencies": { + "dotenv": "^16.4.5" } } diff --git a/sidebarsDocs.js b/sidebarsDocs.js index b644db237..94a78935a 100644 --- a/sidebarsDocs.js +++ b/sidebarsDocs.js @@ -77,6 +77,13 @@ module.exports = { }, ], }, + { + type: 'category', + label: 'Learn LoRaWAN', + link: { type: 'doc', id: 'network-iot/learn/understanding-lorawan' }, + items: ['network-iot/learn/understanding-lorawan', 'network-iot/learn/build-a-device'], + collapsed: true, + }, { type: 'category', label: 'Use Helium LoRaWAN', diff --git a/src/css/custom.css b/src/css/custom.css index f70b4bdb1..e8f79aa3b 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -849,11 +849,17 @@ html[data-theme='dark'] .badge--secondary:hover { } .screensnippet-wrapper .add-shadow { - box-shadow: 0px 1.4px 5.2px -10px rgba(0, 0, 0, 0.004), 0px 2.3px 8.2px -10px rgba(0, 0, 0, 0.006), - 0px 3px 10.5px -10px rgba(0, 0, 0, 0.008), 0px 3.5px 12.6px -10px rgba(0, 0, 0, 0.009), - 0px 4px 14.7px -10px rgba(0, 0, 0, 0.01), 0px 4.4px 16.8px -10px rgba(0, 0, 0, 0.011), - 0px 5px 19.1px -10px rgba(0, 0, 0, 0.012), 0px 5.8px 21.8px -10px rgba(0, 0, 0, 0.014), - 0px 7px 25.6px -10px rgba(0, 0, 0, 0.016), 0px 10px 36px -10px rgba(0, 0, 0, 0.02); + box-shadow: + 0px 1.4px 5.2px -10px rgba(0, 0, 0, 0.004), + 0px 2.3px 8.2px -10px rgba(0, 0, 0, 0.006), + 0px 3px 10.5px -10px rgba(0, 0, 0, 0.008), + 0px 3.5px 12.6px -10px rgba(0, 0, 0, 0.009), + 0px 4px 14.7px -10px rgba(0, 0, 0, 0.01), + 0px 4.4px 16.8px -10px rgba(0, 0, 0, 0.011), + 0px 5px 19.1px -10px rgba(0, 0, 0, 0.012), + 0px 5.8px 21.8px -10px rgba(0, 0, 0, 0.014), + 0px 7px 25.6px -10px rgba(0, 0, 0, 0.016), + 0px 10px 36px -10px rgba(0, 0, 0, 0.02); } .screensnippet-wrapper:has(.add-shadow-margin) { @@ -868,15 +874,16 @@ html[data-theme='dark'] .badge--secondary:hover { border-radius: 18px; } -.docslabEditor { - width: 70%; - height: 300px; +.screensnippet-wrapper:has(.docslabEditor) > div { + width: 100%; } -.font-disable-calt { - font-feature-settings: 'calt' 0, 'calt' 0; +.screensnippet-wrapper .docslabEditor { + width: 100%; + max-height: 550px; + height: 50vh; } -.footer { - background-color: #13162e; -} +.font-disable-calt { + font-feature-settings:'calt' 0,'calt'0; +} \ No newline at end of file diff --git a/src/theme/HntToFeeSol.tsx b/src/theme/HntToFeeSol.tsx index 3abec7198..f4d72806e 100644 --- a/src/theme/HntToFeeSol.tsx +++ b/src/theme/HntToFeeSol.tsx @@ -58,7 +58,7 @@ export const HntToFeeSolImpl = () => { return ( {error && ( - +

{error.message}

diff --git a/src/theme/HstManager.tsx b/src/theme/HstManager.tsx index d8a383790..05ccb371a 100644 --- a/src/theme/HstManager.tsx +++ b/src/theme/HstManager.tsx @@ -187,7 +187,7 @@ export const HstManagerImpl = ({ idl }: { idl: Fanout }) => { return ( {error && ( - +

{error.message}

diff --git a/src/theme/LedgerMigration.tsx b/src/theme/LedgerMigration.tsx index 7a89118a7..ffb97478f 100644 --- a/src/theme/LedgerMigration.tsx +++ b/src/theme/LedgerMigration.tsx @@ -351,7 +351,7 @@ export const LedgerMigration = () => { component: ( {errorSolana && ( - +

{errorSolana.message}. Please make sure you are connected to the Solana Ledger App @@ -398,7 +398,7 @@ export const LedgerMigration = () => { component: ( {errorHelium && ( - +

{errorHelium.message}. Please make sure you are connected to the Helium-Solana @@ -429,7 +429,7 @@ export const LedgerMigration = () => { component: ( {errorSeed && ( - + {errorSeed.message} @@ -452,7 +452,7 @@ export const LedgerMigration = () => { component: ( {errorHeliumSign && ( - + {errorHeliumSign.message}. Please make sure you are connected to the Helium-Solana Ledger App and have blind signing enabled. @@ -476,7 +476,7 @@ export const LedgerMigration = () => { component: ( {errorSolanaSign && ( - + {errorSolanaSign.message}. Please make sure you are connected to the Solana Ledger App and have blind signing enabled. @@ -500,7 +500,7 @@ export const LedgerMigration = () => { component: ( {errorSendTransactions && ( - + {errorSendTransactions.message} diff --git a/src/theme/components/Alert.tsx b/src/theme/components/Alert.tsx index 77cf1037b..b51582ac6 100644 --- a/src/theme/components/Alert.tsx +++ b/src/theme/components/Alert.tsx @@ -1,17 +1,15 @@ import React from 'react' import { FaExclamationCircle } from 'react-icons/fa' - export const AlertIcon: React.FunctionComponent = () => { return } export const Alert: React.FunctionComponent<{ children: React.ReactNode - status: 'error' | 'warning' }> = ({ children }) => { return ( -

+
{children}
) diff --git a/yarn.lock b/yarn.lock index 5721e6f93..cd948fae8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1096,6 +1096,15 @@ resolved "https://registry.yarnpkg.com/@chevrotain/utils/-/utils-11.0.3.tgz#e39999307b102cff3645ec4f5b3665f5297a2224" integrity sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ== +"@chirpstack/chirpstack-api-grpc-web@^4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@chirpstack/chirpstack-api-grpc-web/-/chirpstack-api-grpc-web-4.8.1.tgz#4740cbf274da3e6d7ac41b80af13753de59aec65" + integrity sha512-eIwHp1pXriBX4fKT4Np8taW8BXFrCEqdqIJvp3L5pNueh3JGe6qFOpRUxoL9uXxIO8PUqAzBrJfSMNqDy9ub5w== + dependencies: + "@types/google-protobuf" "^3.15.12" + google-protobuf "^3.21.2" + grpc-web "^1.5.0" + "@clockwork-xyz/sdk@^0.3.0": version "0.3.4" resolved "https://registry.yarnpkg.com/@clockwork-xyz/sdk/-/sdk-0.3.4.tgz#fba9efd4183172c560f2566e42126e021a1e5f4d" @@ -3291,6 +3300,11 @@ resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a" integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg== +"@types/google-protobuf@^3.15.12": + version "3.15.12" + resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.12.tgz#eb2ba0eddd65712211a2b455dc6071d665ccf49b" + integrity sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ== + "@types/gtag.js@^0.0.12": version "0.0.12" resolved "https://registry.yarnpkg.com/@types/gtag.js/-/gtag.js-0.0.12.tgz#095122edca896689bdfcdd73b057e23064d23572" @@ -3745,9 +3759,9 @@ accepts@~1.3.4, accepts@~1.3.8: negotiator "0.6.3" ace-code@^1.23.4: - version "1.38.0" - resolved "https://registry.yarnpkg.com/ace-code/-/ace-code-1.38.0.tgz#e6825c6dbc97bb1a44d75bcd876cddab6a73001e" - integrity sha512-iZtpjAOBWsWka3auM7n2MM81ykCsQk0iSVRRwMiPDMOjnLBNm1bQxDVZkx82y21Oo3s3/5HJgHxf/HWcMwHAyw== + version "1.24.1" + resolved "https://registry.yarnpkg.com/ace-code/-/ace-code-1.24.1.tgz#129e070699e33d819a780c69be3b95e0a59e1342" + integrity sha512-pRdEBQpwnXcH4l9BRhevjkICdKt3m7YEN5O+XJ4xtEpqZS5fUt9ZIXlb3UmLU9wl+tYAXm+IN1p0UVUS+C9DWQ== acorn-jsx@^5.0.0: version "5.3.2" @@ -5538,6 +5552,11 @@ dot-prop@^6.0.1: dependencies: is-obj "^2.0.0" +dotenv@^16.4.5: + version "16.6.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -6308,6 +6327,11 @@ globby@^13.1.1: merge2 "^1.4.1" slash "^4.0.0" +google-protobuf@^3.21.2: + version "3.21.4" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.4.tgz#2f933e8b6e5e9f8edde66b7be0024b68f77da6c9" + integrity sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ== + gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" @@ -6350,6 +6374,11 @@ gray-matter@^4.0.3: section-matter "^1.0.0" strip-bom-string "^1.0.0" +grpc-web@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/grpc-web/-/grpc-web-1.5.0.tgz#154e4007ab59a94bf7726b87ef6c5bd8815ecf6e" + integrity sha512-y1tS3BBIoiVSzKTDF3Hm7E8hV2n7YY7pO0Uo7depfWJqKzWE+SKr0jvHNIJsJJYILQlpYShpi/DRJJMbosgDMQ== + gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -11431,9 +11460,9 @@ xterm-addon-fit@^0.7.0: integrity sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ== xterm@^5.2.1: - version "5.3.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0.tgz#867daf9cc826f3d45b5377320aabd996cb0fce46" - integrity sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg== + version "5.2.1" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.1.tgz#b3fea7bdb55b9be1d4b31f4cd1091f26ac42afb8" + integrity sha512-cs5Y1fFevgcdoh2hJROMVIWwoBHD80P1fIP79gopLHJIE4kTzzblanoivxTiQ4+92YM9IxS36H1q0MxIJXQBcA== y18n@^4.0.0: version "4.0.3"