Professional Documents
Culture Documents
Peer-to-Peer Web Applications v4
Peer-to-Peer Web Applications v4
Peer-to-Peer Web Applications v4
1 Defining Web3
2 Using blockchain when data must be verified
1
Defining Web3
Web3 is not just blockchain or cryptocurrency. Web3 is social media, streaming videos,
games and every other application that runs over the internet today; but with application
architecture that makes every participant an owner of the system.
As with any generational change in technology, during early phases when you are
unsure what a technology will ultimately become—and before the infrastructure to
support it has matured—the first introduction to such generational change is just
marketing: a promise, a projection, an announcement with a list of all the problems to
be solved …soon. Getting from point A to point B in this context is naturally confusing,
perhaps a bit frustrating, and businesses, a potential risk.
Now imagine in the previous scenario that the infrastructure you are waiting for is
proposed as a replacement for the whole of the internet. Where to begin? What does that
even mean? The good news is that the internet is not being replaced. It turns out that
the internet infrastructure that you have is already pretty great and continues to evolve.
Even better, you have seen this transition before and have practiced it, because you are
already at Web2!
The conclusion of that original transition, which feels so natural today, is that the
original internet underpinnings were brought along for the ride; we still had use for them.
The static HTML pages of the Web1 era over time were enhanced with Javascript-
powered interactivity, user-generated content, and an ad-supported economic model
which all used the humble HTML page as a base. Web1 didn’t cease to exist as the world
ran full speed into Web2, it came along for the ride. Attempting to pick a date, company,
or technology that marked the end of Web1 and beginning of Web2 misses the point. It’s
at the point that the structural capabilities of the web ecosystem evolve to such an
extent that it changes our expectation of what the internet could and should be that
marks generational change. You are undergoing this next shift in collective expectations,
today you are on our way in the transition to Web3.
This book is intended to be an on-ramp, a holistic view of Web3 application
architecture and what it means for building the applications that take advantage of the
enhancements that Web3 brings; both with, and without blockchain. To do this,
throughput this book, application architecture is presented as a series of layers as shown
in figure 1.1.
Figure 1.1. When building Web3 applications, you are able to customize how you build each layer, based
on your application’s functionality and technical needs. Each of these layers is explained in detail with
dedicated chapters in this book.
After learning what defines Web3, you will practice building Web3 applications layer-by-
layer as you progress through this book.
A user installs, operates, or engages with a Web3 system to the extent that they find
worthwhile utility in that system. Rather than a facility to exchange cryptocurrency, Web3
should be thought of in a more agnostic context as an exchange of utility , which may or
may not include direct financial payment. A Web2 example of this is torrent file sharing.
Such torrent systems function by accessing files in multiple parts that are downloaded in
parallel. This parallel download means that when operating a torrent client, a user
receives the utility of faster downloads. In exchange, the torrent application being used
may also support the re-transmission of that content, allowing the user the choice of
providing that same utility to others. When agreeing to retransmit downloaded content, a
portion of that systems infrastructure becomes user-generated without direct payment
being made. Web3 technologies such as blockchain build on top of this, mechanics that
enable identity, entitlements, and payments in peer-to-peer formats that were not
previously possible, and in doing so, carry forward the already existing peer-to-peer
architecture of Web2, making it inseparable and core to Web3.
For the purposes of this book, I define Web3 as the following: An evolution of the
existing internet in which systems support verifiable self-sovereign identity in addition to
operating peer-to-peer (figure 1.2).
Figure 1.2. Web3 is inclusive of applications, services, infrastructures, and further mechanics that are
internet connected; so long as they support verifiable self-sovereign identity and are peer-to-peer in
nature.
The wording in this definition is intentional, both for what it includes and what it leaves
out. Let us take a closer look at its component parts to understand why this definition is
required and how it is different from Web2 designs.
1.1.2 Peer-to-peer
Peer-to-peer as a paradigm has existed for a significant portion of Web2’s development,
and here it is not the new addition. What has changed in Web3 context is that peer-to-
peer is the default mode of operation (figures 1.3 and 1.4). Defining Web3 using the term
peer-to-peer, rather than decentralized, also solves a few semantic problems. First it is
clear about the technologies and methods preexisting in Web2 that are being reused
(peer-to-peer architecture). The second is that it allows the definition to be as close as
possible to a binary state and nullifies any debate over a threshold for when when a
system can be considered decentralized. A system operates peer-to-peer or it does not.
The Bitcoin blockchain and Bittorrent are examples of systems that operate peer-to-peer.
Another example that you may be actively using today is the Windows Update function of
the Microsoft Windows 10 and later operating systems. Starting with Windows 10,
Microsoft has introduced an option named "Delivery Optimization" which allows Windows
Update file downloads to be completed peer-to-peer, in-part to reduce network utilization
during updates within large campus and enterprise environments. Throughout this book,
the term peer-to-peer is used in place of any use of the word "decentralized".
NOTE For more information about the Delivery Optimization feature of Microsoft Windows, see
What is Delivery Optimization? (http://mng.bz/d1Gw)
Figure 1.4. Peer-to-peer systems allow participants to connect to each other without a central point of
control. Web3 applications, as a default, exchange information peer-to-peer.
NOTE For more information about zero knowledge cryptography and zero knowledge proofs, see
Real-World Cryptography (https://www.m anning.com/books/real-world-cryptography)
Mastodon is a social messaging application that allows users to create accounts and
post messages both publicly and privately, similar to Twitter. The Mastodon application is
open source and is run on servers operated by the community. Mastodon users can
create an account on one server, while following and messaging accounts from other
servers. If a user does not like the experience of any single Mastodon server, such as if
a server is slow; the user can export their account and import it to a different Mastodon
server. The user can even launch their own Mastodon server to import their account
data, allowing the identity to be self-sovereign. A more recent feature of Mastodon is the
ability to verify user identity. Rather than rely on a trusted third party for verification,
Mastodon allows users to add a line of code to a personal website which serves as a
secondary verification of identity. As long as the Mastodon server can read the
verification code, a green check mark is placed alongside the website link on the user’s
profile. Following the definition presented in this chapter, Mastodon is part of Web3.
PeerTube is another fediverse application that allows users to create accounts on
community-operated servers that are connected peer-to-peer. Users of the PeerTube
application can upload videos to personal channels and follow the channels of others,
similar to YouTube. Unlike Mastodon, at the time of writing, PeerTube does not allow
users to export their accounts to move themselves to another PeerTube server. Because
identities created on PeerTube are not self-sovereign, the application is not considered
Web3 (figure 1.5).
Figure 1.5. Although fediverse applications operate peer-to-peer, only fediverse applications supporting
self-sovereign identity can be considered Web3.
Regardless of the specific application, most any system can be architected entirely on
a Web3 architecture. When defining the system architecture, whether for Web2 or Web3,
you must compare tradeoffs among different alternatives. If you are already operating a
Web2 application or service, some items to consider that could help in making the
decision of whether it is time to migrate your system to Web3 architecture are:
1.6 Summary
Web3 is defined as: An evolution of our existing internet in which
systems are peer-to-peer and support verifiable self-sovereign identity.
Choosing to be anonymous is a form of self-sovereign identity. Web3
does not include data ownership or data privacy by default …yet.
Web3 is an exchange of control, but also an exchange of utility among
a community of participants.
Application and infrastructure code are no longer separate. Controls for
storage, compute, and routing are now directly included in Web3
application code.
Web3 and Web2 are not mutually exclusive. Use cases will continue to
exist where a central authority is preferred or mandated through
regulation.
Whether you should build your application for Web3 will depend
on your individual use case. Some common considerations are:
Are you already a user or supporter of open source software?
Do you want to operate a business based on your software?
What model do your users prefer?
What levels of performance are required?
What existing problems do you have that a Web3 design will
solve?
Not all Web3 applications require a blockchain. In Web3, blockchain can
be used to solve the more narrow problem of data verifiability and
trust.
2
Using blockchain when data
must be verified
After reading Chapter 1, you now know the concrete definition of Web3 and an
introduction to how it is changing application architecture. Although Web3 is much larger
than just blockchain; blockchain still plays a large role in Web3, by making mechanics
such as payments, able to operate peer-to-peer. In this chapter, you will get an extended
explanation of all things blockchain. How they function underneath, what makes them
secure, their performance, as well as direct comparisons of the now extensive range of
blockchain formats and features. By the end of this chapter, you will have a solid
foundation to investigate and choose blockchains to use in your next Web3 project …if
required.
Figure 2.1. In a blockchain, data stored within a data block is hashed, and the resulting hash is placed
into the next block that is added. When data is chained together in this way, a record stored in a
blockchain block cannot be changed later, unless a majority of participants accept the change and agree
to update all the blocks that were added after to include a changed summary hash. Because of this
required agreement or consensus, records stored in a blockchain are considered tamper-resistant, even
though they have no central owner.
Figure 2.2. If a block within a blockchain system is ever modified, it could be confirmed as invalid due to
its hash no longer matching the hash which was recorded into blocks that were created after.
NOTE In a PoW network, it is also possible that two participants produce an acceptable hash
value at the same moment; in this scenario, a fork occurs in which participants accept and begin
building on the valid hash and block that was received first. The most common way this conflict is
resolved is using a "longest chain rule". Additional information about PoW conflict resolution and
the longest chain rule can be found within the original Bitcoin whitepaper: https://bitcoin.
org/en/bitcoin-paper.
One of many alternatives to PoW is BFT. Unlike PoW, which is designed entirely for open
participation, BFT consensus mechanisms are commonly configured to accept updates
with a lower amount of participant agreement; 33% for example in place of the 51%
threshold used in Bitcoin networks' PoW consensus. This change allows blockchain using
BFT consensus to reach an agreement and therefore apply updates faster. This increase
in performance is significant in that it unlocks the ability to use blockchain with
applications that require more frequent updates, or high transaction rates. The tradeoff
that is made in the case of BFT, is that overall security is reduced. If agreement can be
reached with only 33% of participants, then an attacker would only need to have control
of 34% of participants in the system to control which updates are accepted. This format
of higher transaction capacity and reduced security makes BFT-based blockchain
systems popular in private or consortium deployment, where there is still no central
owner, but participation is controlled or otherwise limited to known parties.
Going forward in time, one participant realizes that they can edit the rules of the
application so that they never have to pay for heat. Their version of the application is
now set so that payment for heat is due when the temperature is below -32o Fahrenheit
(-35.5o C) so that a payment is never due. Both participant’s applications, in this case,
continue to report that all required payments have been made.
This previous example demonstrates one of the largest limitations of systems that
operate peer-to-peer, which is a lack of authority or ability to enforce rules. In systems
where there is a natural incentive to collaborate, combined with little or no upside for
misbehaving, the need for rule enforcement can be removed or greatly reduced. The
streaming radio application you will build in the next chapter is an example of this. Its
application functions are limited to playing, pausing, and changing between predefined
stations. There is very little to be gained by modifying the application to make it appear
paused when it is playing. If your Web3 application can be damaged or exploited by
misbehavior, using a blockchain to make records verifiable is the primary way issues of
collaboration are resolved in Web3. Some Web3 application examples where blockchain
can be used for verifiable records include:
Voting systems
Identity and reputation systems
Financial systems
Supply chain and logistics systems
Legal and contract systems
These categories of applications are not intended to be exhaustive, and within each of
them are several sub-classes of application. Once you have chosen a Web3 application
idea and have identified that portions of its application data must be verifiable, the next
step in defining the architecture of your application is identifying the blockchain and
blockchain ecosystem you will use. The following section provides a menu of sorts, for
the ecosystem of blockchain technologies available in Web3.
Figure 2.3. Comparison of popular layer 1 blockchains, based on reported peak TPS at the time of writing.
CURRENCY SUPPORT
Most all layer 1 blockchains incorporate a native digital currency and impose a fee for all
activities occurring on the chain (one exception being the Hyperledger project). If you
are building a system that will charge users for access, you will need to consider
currency impacts including liquidity, price stability, and transaction costs. A currency that
fluctuates significantly in price or carries high transaction costs, can impact users'
ongoing ability to interact with your Web3 application.
Today a category of currencies labeled stablecoins has been introduced in part to
compensate for such currency price fluctuation. A well-known layer 1 blockchain which
supported a native stablecoin was the Terra blockchain. The Terra blockchain was known
as an algorithmic stablecoin, meaning both the stablecoin itself and the asset that backed
it (the Luna digital currency in this case), are issued by the layer 1 blockchain. Layer 1
blockchains such as Terra, which support algorithmic stablecoins, rely on a set of
algorithms that maintain its stablecoin price by dynamically increasing and decreasing
the supply of the backing digital currency to compensate for changes in demand. It
should be noted that the Terra blockchain halted service in 2022, and at the time of this
writing, none of the 10 layer 1 blockchains mentioned in figure 2.3 support a layer 1
issued stablecoin. An alternative to an algorithmic stablecoin, is stablecoins that are
backed by government-issued currencies. Such government asset-backed stablecoins
are centralized however and are discussed in greater detail later in this chapter.
Table 2.1. A comparison of smart contract support among 10 popular layer 1 blockchains along with their
smart contract programming language
STATE CHANNELS
State channels allow a fixed group of participants to send transactions to each other off-
chain. A state channel is opened by deploying a smart contract to a layer 1 blockchain,
which accepts an initial deposit from participants (figure 2.4). This smart contract
contains all logic that will govern interactions within the state channel. Note that
deployment of a root smart contract containing governing logic is the method used by all
layer 2 systems to handle settlement to layer 1 blockchains underneath.
After the initial deposit, the state channel is considered open and participants can
complete transactions with each other outside of the layer 1 blockchain. All transaction
that happens outside of the layer 1 includes a sequence number as well as any balance
changes and cryptographic signatures from participants to certify authenticity. When the
parties have completed their activities and no longer need to transact with each other,
closing transactions are sent to the payment channel smart contract to settle the state
and final balances of the participants on the layer 1 blockchain. Within the state channel
smart contract, the logic can allow it to be closed by a single participant, all participants
or some combination in between. Figure xx shows a graphic representation of state
channel transaction flows.
Figure 2.4. A state channel can be opened between two or more participants, however, all participants
must be pre-defined within the opening layer 1 smart contract.
Although payment transactions are the most common use for state channels, they can
also be used with smart contract state changes. The Bitcoin lightning network is an
example of a layer 2 that utilizes state channels.
SIDE CHAINS
A side chain, as the name implies, is an entire parallel blockchain. Side chains operate
almost entirely as standalone blockchains, but periodically record their state to an
underlying layer 1 chain to inherit the layer 1 blockchain’s security. You can think of this
as taking a backup and saving it onto the more secure storage provided by the layer 1
blockchain (figure 2.5). Because side chains are fully operational blockchains on their
own, they also come with a variety of consensus models, performance attributes, and
features; including smart contract support.
With a side chain, the periodic snapshot that is sent to the underlying layer 1
blockchain is determined entirely by its own consensus and security configuration and
only contains a representation of the layer 2 state at a point in time. A proof or
guarantee is not required; the configuration and security of the layer 2 is being trusted
to work properly.
A popular example of an Ethereum side chain is the Polygon blockchain. The Polygon
blockchain uses PoS consensus, similar to Ethereum, but can achieve much higher
throughput, estimated at 65,000 transactions per second compared to 30 transactions
per second at the Ethereum layer 1. Because Polygon is EVM compatible, smart
contracts written in the Solidity language for Ethereum are generally compatible with the
Polygon blockchain.
Figure 2.5. A layer 2 side chain can significantly improve throughput by submitting condensed summary,
or "checkpoint" blocks to the layer 1 blockchain underneath
PLASMA CHAINS
Plasma chains were designed as a more purpose-built side chain for the Ethereum layer
1 blockchain. A plasma chain works like a side chain, but with improved security and a
focus on non-fungible tokens (NFT).
A fungible item is any item that is not unique or where there exist copies of the item.
The US dollar is an example of an item that is fungible. A non-fungible item is considered
unique and not substitutable. In the context of blockchain, non-fungible items are
represented digitally on-chain as non-fungible tokens. A digital token on a blockchain can
be made non-fungible through the inclusion of unique metadata such as a serial number.
NOTE Beyond including unique identification, NFTs are digital tokens like any other token issued
on a blockchain. The programming required to include NFTs in a Web3 application is not
substantively different from other blockchain digital tokens, and for this reason, NFTs are not
covered in depth in this book. For more information about the history and development of NFTs,
visit: https://ethereum.org/en/developers/docs/standards/t okens/erc-721/.
When using a plasma chain, the snapshot that is placed onto the layer 1 blockchain
includes proof of the state, by providing a cryptographically signed record of all prior
transactions, up to the moment of the snapshot. Although a proof of state is provided,
the layer 1 blockchain does not perform verification on the proofs that are provided;
they are assumed to be valid. In addition to providing cryptographic proof of
transactions, plasma chains often include a settlement or "challenge" period of a week or
more, to allow transactions to be disputed, if the layer 2 blockchain is compromised or
there is a dispute regarding the states which were submitted.
ROLL-UPS
Layer 2 roll-up blockchains inherit the capabilities of both state channels and plasma
channels and add to these an ability to verify the cryptographic proofs that are submitted
as summaries to the layer 1 blockchain underneath. Layer 2 roll-ups achieve this by
programming into the governing layer 1 contract, additional logic that at a minimum can
validate that the transactions being referenced in the summary block as publicly
available. This guarantees that any parties that have transaction disputes have the data
to verify the correct state.
Layer 2 roll-up blockchains come in two categories, based on whether the source
transactions are public or private. The first variety, optimistic rollups, supports the
minimum requirement of guaranteeing source transaction data is available publicly. In
the optimistic category are Ethereum layer 2 blockchains such as Arbitrum and
Optimism. These layer 2 blockchains are termed "optimistic" because they typically
operate consensus models with reduced security which is optimistic about the validity of
transactions and does not generate a cryptographic proof for each transaction. Because
of this, layer 2 optimistic rollups typically enforce a settlement period like Plasma chains.
The second layer 2 rollups is zero-knowledge rollups or zk-rollups. zk-rollups
guarantee that source transaction data is publicly available for verification. Unlike
optimistic rollups, however, zk-rollups provide the source transaction data in the form of
the zk-proofs which were described earlier in the chapter. Using this format has two
benefits, the first is that by generating a proof for each transaction, zk-rollups do not
require a settlement window for disputes; transactions are proven as they happen. The
second benefit is that by using a zk-proof specifically, the contents of the transaction are
allowed to remain private. Two examples of layer 2 zk-rollups are StarkWare and
zkSync. Both of these projects rely on Ethereum as their layer 1 blockchain.
Because the difference among layer 2 blockchains is minor, table 2.2 is provided as a
single summary of how the difference layer 2 blockchain implementations compare.
Optimistic
State channel Side chain Plasma Chain zkRollup
Rollup
Deployed with
a smart Yes Yes Yes Yes Yes
contract
Permissioned
Yes Yes No No No
participation
Permissionless
No No Yes Yes Yes
participation
Snapshots
include No No Yes Yes No
transactions
Proofs are
- - Yes No Yes
verifiable
Has
settlement Yes Yes Yes Yes ^ No
period
Answering these questions can give a more complete picture of the long-term health and
holistic performance that can be expected when choosing a blockchain to work with.
CeFi providers may also offer services beyond buying and selling cryptocurrencies. A
CeFi service that has gained popularity in recent years is the issuance of stablecoins. A
stablecoin is a cryptocurrency that is designed to have a stable price, and most
commonly achieves this by being backed by other assets such as the US dollar.
Stablecoins may even be backed by other cryptocurrencies or non-currency assets such
as real estate. Stablecoins are designed to address the issue of price volatility that is
common among cryptocurrencies; when an exchange or coordinated entity manages
assets to stabilize the price, the stablecoins cannot be considered Web3.
NOTE There do exist stablecoins which are Web3 and re-balance backing assets automatically
using smart contracts. These cryptocurrencies are referred to as "algorithmic stablecoins".
To further explore the idea of a Web3 that is free, the next section of this book takes
a “blockchain optional” approach to Web3 application architecture and will teach you the
Web3 application architecture of a free streaming music service named Code Radio.
2.5 Summary
Blockchain allows peer-to-peer applications to support new services
such as payments and identity, due to its tamper resistance.
Blockchains are often categorized as layer 1 or layer 2. Layer 1
blockchains perform slower and have higher security, while layer 2
blockchains are typically faster and are paired with a layer 1 blockchain
to improve security.
TPS speed can vary greatly among blockchain projects, ranging from 6
TPS on the low end to highs above 8,000+ TPS.
Layer 1 blockchains can run smart contract code either natively, or
within a virtual machine.
Some layer 1 blockchain projects are built to be EVM compatible and
can support Ethereum smart contract code.
Zero knowledge cryptography and ZKPs enable layer 1 blockchains
such as Mina and Zcash to support private transactions.
State channels are a layer 2 blockchain technology that enabled a fixed
group of participants to complete transactions off-chain and record a
final balance to a smart contract when all activities are complete.
Side chains, plasma chains, and roll-ups are examples of layer 2
blockchain systems.
Layer 2 roll-ups blockchains are available in two varieties: optimistic
and zero knowledge.
Not all systems and services that use blockchain are Web3. Some
examples of these include centralized finance applications as well as
blockchains-as-a-service.
Including a blockchain within a Web3 application is optional. Web3
applications that are free and do not include blockchain are an area
that is currently underdeveloped.
3
Designing applications that
run client-side
This chapter details the application architecture of an example application; a Web3 music
streaming service named Code Radio . Code Radio is a Web3 system designed to take
advantage of the benefits of running peer-to-peer, but does not include a blockchain or
digital tokens. Code Radio is an example of a Web3 application that is designed to be
accessed without payment, similar to a majority of internet services today.
Additionally, this chapter introduces the Javascript programming that underpins the
Code Radio Web3 application, including accessing the HTML Audio API for music
playback.
Whether it is a single page or a complex multi-layered application, if a system needs
to display dynamic content in a browser, it is very likely that it relies on Javascript to
handle this processing. Because of the ubiquity Javascript has earned as a default
programming language for Web, I’ve chosen it for the programming of Code Radio and
as a showcase for the construction of applications for Web3.
With almost three decades having passed since its initial release in 1995, Javascript
has a very large and still expanding ecosystem of libraries, frameworks, and tooling that
allows building applications as complex as a fully featured operating system. To lower
the barrier for reuse however, this chapter makes use of standard, or vanilla Javascript.
The code examples presented in this chapter are intended to present a starting point that
can and should be reused in later projects with the frameworks and libraries of your
choice.
NOTE For more information about defining application feature sets, see User Story Mapping:
Discover the Whole Story, Build the Right Product (https://www.oreilly.com/library/view/user-
story-m apping/9781491904893/)
Returning to our Code Radio example; the problem we are solving is the difficulty of
finding music to play in the background which can help users focus while programming.
Framing this in the form of Five W’s becomes:
Who: Programmers
What: Background music
When: While programming
Where: At a desktop or laptop computer
Why: To reduce distractions and improve focus
After using the Five W’s to narrow the applications' focus, the following items become a
minimally required feature set to solve the users' problem:
The ability to play and pause music
The ability to choose between two or more radio stations
For Code Radio, the music being played are recordings that are in the public domain.
Public domain music refers to songs not protected by copyright which do not require
payment for performance or distribution. For this reason a blockchain to track payment
or ownership is not required. Code Radio does however, still benefit from a Web3
architecture of running peer-to-peer. With a minimum feature set defined, the following
section maps this to a user interface design.
NOTE Interface design is a broad field and is presented here only at a high-level in the context of
the Code Radio application. For more information about application design, see Design for
Developers (https://www.m anning.com/books/design-for-developers).
Figure 3.1. The code radio interface is single-purpose, with no additional menus or navigation. The user is
asked to learn a single interaction of clicking or touching a channel. Interactions for starting, stopping,
and changing channels are handled using this single interaction.
With consideration given to its visual design , interaction design, and information
architecture, figure 3.1 shows the chosen Code Radio UI. At this point in the architecture,
you now have a defined feature set and have mapped these features to an interface
design. The next step in Code Radio’s application architecture is to identify the
programming language(s) that will be used in order to plan the packaging, distribution,
and infrastructure required for the application to operate. Remember that this all
happens before writing the your first line of code.
Table 3.1. With knowledge of the architecture differences between a centrally-managed application and
an application designed for Web3, the design chosen for Code Radio then becomes the input that defines
its application architecture.
Finally, figure 3.2 shows a second summary, this time, for the connectivity of a new user
starting the application for the first time, as well as the existing connectivity among
participants who have previously discovered each other using the application’s
embedded seed list. In final form, all centrally-managed cloud infrastructure is replaced
within the Code Radio application design.
Figure 3.2. By building Code Radio for wbe browser compatibility, a users machine resources helps
increase the capacity of the application network as long as they have their browser open.
cd ./web3-application-architecture/chapter_3/chapter_3_begin/code_radio
From within this directory, start the Node.js web server using the command:
http-server
With this command, Node.js starts a web server on your local machine and return output
that shows the IP address and port that it has assigned. Listing 3.1 shows the output of
running this command on Mac OS.
code_radio % http-server
Starting up http-server, serving ./
http-server settings:
CORS: disabled
Cache: 3600 seconds
Connection Timeout: 120 seconds
Directory Listings: visible
AutoIndex: visible
Serve GZIP Files: false
Serve Brotli Files: false
Default File Extension: none
Available on:
http://127.0.0.1:8080 #1
http://192.168.125.169:8080 #1
Hit CTRL-C to stop the server
NOTE If you have executed the previous instructions on a dedicated server or already have
another web server operating on your machine, it is possible that the default http-server port of
8080 is not available. To resolve this, an alternative port can be specified by including "-p [PORT
NUMBER]" after the web server command. For example "http-server -p 8081". For a complete list
of settings that can be used with http-server, visit the package documentation page on NPM at:
https://www.npmjs.com/package/http-server.
Now that your web server is online, copy and then paste one of the web addresses from
the output into your web browser. The Code Radio application should load as shown in
figure 3.3.
Figure 3.3. As a starting point, the Code Radio interface has been provided. At this stage Code Radio is
just a static web page with no functionality. Additional Javascript is required to turn this web page into a
functioning application.
After you have loaded Code Radio in your web browser, back at your command line, you
see additional log output showing resources being accessed on the page, similar to those
shown in listing 3.2. This output can be used for debugging your application by
confirming if the correct resources are being loaded by http-server as well as checking
for any errors reported by http-server in cases where your application code does not
function as expected.
NOTE In future projects, if http-server loads a directory in the web browser instead of your Web3
application, this generally means that the index.html file that is used as a starting point by http-
server is not found. Ensure that your applications' starting screen is named index.html and that
you launch http-server from the directory containing the index.html file to avoid error.
If you have not done so already, take a moment to click the radio buttons and interact
with the Code Radio. The application is not functional in this case, because additional
Javascript is required to power the logic and interactions of the application. You begin
adding this Javascript functionality in the next section.
Figure 3.4. Web3 applications remove all centralized servers from the application flow.
In this section you add the client-side Javascript logic that allows the Code Radio
application to handle music playback. Adding the additional logic to the Code Radio
application starts by creating a Javascript code file and updating the existing HTML page
to load it.
To add Javascript to the Code Radio application, create and save a new file in the
project root directory named "radio.js". The project root folder should now contain a
media folder, as well as files radio.css, index.html, and radio.js.
Before adding any Javascript to the new file, return to the file index.html and add the
following line as the last item of the HTML body:
<script src="./radio.js"></script>
Adding this line instructs the HTML page to also load the contents of the Javascript file
when the page loads. In a web application, Javascript is render blocking, meaning the
browser waits until it has completely loaded the defined Javascript file before loading any
parts of the HTML page that come after. For this reason the Javascript file is placed at the
end of the HTML body, allowing the Code Radio UI to fully load before the loading of the
script. The updated index.html file should now match listing 3.3.
<!doctype html>
<html>
<head>
<title>Code Radio</title>
<meta name="description" content="Free streaming radio for programmers">
<meta name="keywords" content="Web3 tutorial application">
<link rel="stylesheet" href="./radio.css">
</head>
<body>
<div class="main">
<div class="radio_text">
<h1>Choose Your <span class="radio_header">Vibe</span></h1>
<div class="radio_parent">
<div class="radio_box"><button class="bn30" id="btn-
classic">Classic</button></div>
<div class="radio_box"><button class="bn30" id="btn-
ambient">Ambient</button></div>
<div class="radio_box"><button class="bn30" id="btn-
lofi">LoFi</button></div>
</div>
<br>
<p>Code Radio is a decentralized demo application for the book Web3
Application Architecture <br><br>
(Steven Platt, Manning Publishing 2023)</p>
</div>
</div>
<script src="./radio.js"></script> #1
</body>
</html>
For this chapter, this is the only time updating the HTML code is necessary. The
remainder of this chapter happens entirely within your Javascript file radio.js. The sky is
the limit for the level of customization and compute operations that you can add in as
Javascript. For the Code Radio application, you are adding the following functionality:
For the remaining two radio buttons; within the HTML file, the "Ambient" radio button
carries an ID of "btn-ambient", while the "Lofi" button carriers an ID of "btn-lofi".
Because each of the radio buttons are operated separately, you also need to create
similar event listeners for the other two buttons, using their unique HTML IDs. After
adding these event listeners, all three of the buttons within Code Radio will be
programmed to call an associated Javascript function when clicked. When completed
your event listener Javascript code should match that in listing 3.5.
Within your radio.js file, update the code to assign the HTML ID btn-ambient to a new
variable named "ambient_audio". Followed by the corresponding event listener which
after a click event, calls the Javascript function "play_ambient".
Finally, for the Lofi button of Code Radio, update the code to assign the HTML ID btn-
lofi to a new variable named "lofi_audio". Followed by the corresponding event listener
which after a click event, calls the Javascript function "play_lofi".
Listing 3.5. Completed event listener code for all three radio buttons
Once you have registered the event listeners, they will be fully functional only after you
have implemented the corresponding functions invoked by each of them, namely
play_classic(), play_ambient() and play_lofi(). These functions modify the button font
after the button has been clicked.
By changing the font on the radio button when it is clicked, you are giving a visual cue
to the user for which radio channel is active. With the new Javascript functions, the font
is changed by using the classList.add method to add the CSS class "playing" to the
buttons' font as shown in listing 3.6.
Listing 3.6. Logic to change the font of the "Classic" radio button when clicked
function play_classic() {
document.getElementById("btn-classic").classList.add("playing");
}
The Javascript logic in listing 3.6 adds a minimum of functionality to the Classic radio
button. You could repeat this same function for the other buttons, but the Code Radio
application would not have logic to ensure only a single button was active at a time, or
that the user is able to click the button a second time stop a station. Before adding
additional functions for the other two buttons, the original play_classic Javascript function
should be updated once more to add the previously mentioned logic. As a start, add a
new global variable named "classic_playing" to your radio.js file, which stores a boolean
value. For now, this variable can be assigned a default value of false . This
classic_playing variable is global in this case because it will be accessed by other
functions.
From within the play_classic function, you will now add logic that will check the value
of the classic_playing variable to decide what to do. This is done using if/else logic to
handle if classic_playing if true , if it is false , and a final case to pass or skip the function
logic if there is an error. Based on these cases, you then add or remove the CSS class
shown in listing 3.6, as well as add or remove the the CSS class from the other radio
buttons. To visualize the logic, consider these three scenarios:
Scenario 1: If nothing is playing and a radio station button is pressed,
you update the status of the station to confirm it is now playing as well
as change the CSS font applied to the radio station button text.
Scenario 2: If a station is already playing, and the same radio station
button is pressed, the playback is paused. The playback status of not
playing, and the additional CSS font is removed from the station button.
Scenario 3: If a station is already playing, and a different radio station
button is pressed, the playback of the first station is paused. The first
station status is changed to not playing, and the additional CSS font is
removed from the first station button. For the new station button that
was pressed, you update the status of the station to confirm is now
playing as well as change the CSS font applied to the radio station
button text.
Take a moment to apply this logic for the Classic radio station and its corresponding
play_classic function. After updating the logic and including the new classic_playing
global variable, your Javascript function should match the code in listing 3.7.
Listing 3.7. Updated play_classic logic that includes the button state
function play_classic() {
if (!classic_playing){
classic_playing = true; #1
document.getElementById("btn-classic").classList.add("playing");
document.getElementById("btn-ambient").classList.remove("playing"); #2
document.getElementById("btn-lofi").classList.remove("playing");
} else if (classic_playing){
classic_playing = false;
document.getElementById("btn-classic").classList.remove("playing");
} else {pass;}
}
Additional code can now be added for the play_ambient and play_lofi functions. When
these are added, the three functions and additional variables match the code included in
listing 3.8. The play_classic function you just completed has been omitted for brevity.
Listing 3.8. Completed UI logic for the Classic, Ambient, and Lofi Code Radio buttons
...
function play_ambient() {
if (!ambient_playing){
ambient_playing = true;
document.getElementById("btn-classic").classList.remove("playing");
document.getElementById("btn-ambient").classList.add("playing");
document.getElementById("btn-lofi").classList.remove("playing");
} else if (ambient_playing){
ambient_playing = false;
document.getElementById("btn-ambient").classList.remove("playing");
} else {pass;}
}
function play_lofi() {
if (!lofi_playing){
lofi_playing = true;
document.getElementById("btn-classic").classList.remove("playing");
document.getElementById("btn-ambient").classList.remove("playing");
document.getElementById("btn-lofi").classList.add("playing");
} else if (lofi_playing){
lofi_playing = false;
document.getElementById("btn-lofi").classList.remove("playing");
} else {pass;}
}
#1 The application now tracks the state of playback for all three buttons
The Javascript powering Code Radio now has a fully interactive UI with logic to change
each button font when pressed. The additional Javascript logic that was added enables
support for play , pause , and change station features. At this time, you can press Ctrl+C
to stop your instance of http-server if you have not already done so, and reissue the
"http-server" server command to restart it.
Visiting the url in your browser shows the updated version of Code Radio using client-
side compute to handle interactivity in the UI. Notice the behavior of the station buttons
as they are clicked and the station is changed. Your version of Code Radio should match
figure 3.5 with a new yellow font for the selected station.
Figure 3.5. Code Radio now has an interactive UI that changes font colors when the buttons are clicked.
Notice how changing stations by clicking different buttons removes the font styling from the previously
active option.
NOTE By default, most browsers cache downloaded Javascript code. If your local instance of Code
Radio does not behave correctly, try clearing your browser’s cache before restarting http-server
and visiting the application URL.
Code Radio looks slick at this point in its programming, but its most important capability
is still missing; the actual music streams. The following section details the code to handle
audio playback using the HTML Audio API.
In an identical manner to how an event listener was used to update the Code Radio
interface after a click event, an event listener is required here to start a music playback
stream after a station button is clicked. Because both items are being triggered by the
same click event, the event listeners that were programmed in the prior sections can be
reused here. For convenience, the original event listeners have been reproduced in
listing 3.9.
Listing 3.9. The original event listeners are reused here and are not modified
#1 Changing the action triggered by the event listener requires updating the Javascript function that it
calls when triggered.
Recall that once an event listener is triggered, the remainder of the application logic is
held within the Javascript functions that are called. These functions are play_classic,
play_ambient, and play_lofi. It is here that the additional programming to start and stop
music playback should reside.
Before expanding controls within the playback functions, the music source itself must
be defined within the Code Radio Javascript. Using the HTML Audio API, this is done by
declaring new global variables outside of the playback functions. The HTML Audio API
can be used to either with a file on the local disk, or a remote file. In this case, you will
use a remote file as shown in listing 3.10.
NOTE Music used for the Code Radio application has been shared for reuse with the permission of
the musics creator Antwan Griffin. More information about the artist can be found on his LinkedIn
profile page at https://www.linkedin.com/in/antwan-griffin-60aa85196/.
After defining audio sources for Code Radio, the sources can now be used within the
Javascript playback functions using the .play() method of the HTML Audio API. If the
playback is already active and the station button is clicked again, audio playback is
paused using the .pause() method of the HTML Audio API.
Again starting with the Classic station, audio playback can be added to its
corresponding playback function by editing the play_classic function in your radio.js
Javascript file to account for the two scenarios previously mentioned. When complete
your code for the play_classic function should match listing 3.11.
function play_classic() {
if (!classic_playing){
classic_stream.play(); #1
classic_playing = true;
document.getElementById("btn-classic").classList.add("playing");
document.getElementById("btn-ambient").classList.remove("playing");
document.getElementById("btn-lofi").classList.remove("playing");
} else if (classic_playing){
classic_stream.pause(); #2
classic_playing = false;
document.getElementById("btn-classic").classList.remove("playing");
} else {pass;}
}
If at this point you pause to test the Code Radio application, you may notice another
limitation in the Javascript logic. Based on the code in listing 3.11, if a station is already
playing music and you click the button of a second station, there is currently no logic to
stop playback on the other station. This desired enhancement as well as looping audio
playback can be achieved by again updating the playback functions within your radio.js
file to use some additional methods provided by the HTML Audio API.
Starting first with the HTML Audio "loop" method. This method tells the HTML Audio
API to restart playback if it reaches the end of the file and is applied to the audio source.
For example, for the play_classic function, this would be applied to the classic_stream
audio source. Take a moment to apply the loop method to each of the audio sources
defined in your Javascript playback functions. This should be done outside of the existing
if/else logic so that it applies in all cases.
The second HTML Audio method required to complete the described logic is the
"currentTime " method. This method tells the HTML Audio API to reset the playback
position of the audio source to the beginning and is also applied to the audio source. To
always start station playback from the beginning, apply the currentTime method to the
audio sources defined in your Javascript playback functions. Again, this function needs to
be applied outside of the existing if/else logic so that it applies in all cases.
Adding the previous logic brings you to a state where the Code Radio station buttons
can play and pause, loop the audio source, always start playback from the beginning, as
well as change the button text to confirm the playback state. The last step of preventing
two stations from playing at the same time requires logic that matches the logic has
already been implemented for adding and removing CSS styling to the radio buttons.
Indeed, the existing if/else logic can be reused here and updated to add the play() and
pause() methods to control the music sources in addition to the existing controls for
adding and removing the CSS button styling. For convenience the original logic scenarios
are reproduced here to help visualize where play() and pause() logic would be placed
within your playback functions:
Scenario 1: If nothing is playing and a radio station button is pressed,
you update the status of the station to confirm it is now playing as well
as change the CSS font applied to the radio station button text.
Scenario 2: If a station is already playing, and the same radio station
button is pressed, the playback is paused. The playback status of not
playing, and the additional CSS font is removed from the station button.
Scenario 3: If a station is already playing, and a different radio station
button is pressed, the playback of the first station is paused. The first
station status is changed to not playing, and the additional CSS font is
removed from the first station button. For the new station button that
was pressed, you update the status of the station to confirm is now
playing as well as change the CSS font applied to the radio station
button text.
After updating the logic for the Classic station, your play_classic function should match
listing 3.12.
Listing 3.12. Using additional .loop, .pause(), and .currentTime controls from the HTML Audio API
function play_classic() {
classic_stream.loop = true; #1
ambient_stream.pause(); #2
ambient_stream.currentTime = 0; #3
lofi_stream.pause(); #2
lofi_stream.currentTime = 0; #3
if (!classic_playing){
classic_stream.play();
classic_playing = true;
document.getElementById("btn-classic").classList.add("playing");
document.getElementById("btn-ambient").classList.remove("playing");
document.getElementById("btn-lofi").classList.remove("playing");
} else if (classic_playing){
classic_stream.pause();
classic_playing = false;
document.getElementById("btn-classic").classList.remove("playing");
} else {pass;}
}
NOTE The HTML Audio API does not support a .stop() method. Although the .pause() method can
be used in isolation; here it is being used in tandem with .currentTime=0 as a substitute for the
missing .stop() functionality.
This full playback control logic should now be added for the play_ambient and play_lofi
playback functions as well. After adding the additional playback controls, your Javascript
code should match the completed chapter code located within this books github
repository within directory chapter_3/chapter_3_end/code_radio/.
Code Radio is now a functioning application with all logic and computation happening
client-side. A foundation of Web3 application architecture is the remove of all centralized
resources and by moving all application logic into the client application, you have fully
removed the need for a remote web server relying on centrally managed compute from
Code Radio.
At this time, any user who downloads a copy of your current Code Radio application is
able to run the application without restriction, but you may have noticed that while the
user interface code if fully local, the music that is streamed is not, it remains centralized
and coming from a single source. It is lucky in this case then, that Javascript also have
provisions for handling local storage. In the next chapter you will use Javascript to save
the Code Radio music files locally, making the Code Radio application fully self-contained
before programming it connect and share information peer-to-peer in a later chapter. If
you have completed the programming tasks presented in this chapter but have an
unexpected result, you can compare your code with the completed chapter code within
the chapter_3/chapter_3_end/code_radio/ to assist in debugging.
3.6 Summary
Using the Five W’s of who, what, when, where, and why can help to
narrow the scope and define the feature set of your Web3 application.
Existing processes of defining a feature set and developing a user
interface can be reused when building applications for Web3.
Javascript can be used to complete all application computation and logic
directly on the client machine.
The Node.js Javascript runtime can be used in tandem with the http-
server package to test Javascript programming during development.
Javascript event listeners can be used to respond to user interactions
within an application.
Both local files and remote audio sources can be played using the HTML
Audio API.
The HTML Audio API lacks a "stop" method. This behavior can instead
be created by using .pause() and .currentTime methods.
4
Using Javascript for client-
side storage
In this chapter, you’ll continue working with the Javascript programming of the Code
Radio Web3 application from Chapter 3 to add client-side storage to allow it to function
fully offline. Modern web browsers support a number of mechanisms for applications to
store and retrieve data from a user’s local machine. This chapter focuses on two of these
mechanisms: using the Javascript Web Storage and IndexedDB APIs.
Storing data on the user’s local machine can be useful in a number of scenarios,
including but not limited to the following:
Storing user preferences and site customizations, such as UI themes.
Storing and maintaining user activities such as a shopping cart status
between application sessions.
Storing general application data such as image, music, and video
assets locally, so that they load faster in future uses.
Storing general application data such as image, music, and video
assets locally, so that they can be used without an internet connection.
In this chapter, the Javascript Web Storage API is used to store the Code Radio user
preference of a light or dark UI theme, whereas the Javascript IndexedDB API is used to
store the Code Radio music files on the users' local machine. Using Javascript for client-
side storage in this format allows the Code Radio application to function offline and
without centralized cloud storage after its initial boot.
4.1 Storing Web3 application preferences using the Web Storage API
The Web Storage API is a browser API that allows storing simple data, such as strings,
and numbers in the form of key:value pairs on a client’s machine. The limited format of
data that can be stored using Web Storage API makes it ideal for storing simple state
data such as application variable values.
The Web Storage API supports two types of objects, sessionStorage and localStorage .
The first format is used to store data that is useful only during the active session and can
be discarded at the end of a session. A session can be 5 minutes, 5 hours, or longer. An
example of this would be media that is buffered or preloaded during file playback. If you
have ever received a prompt from a streaming service or your bank, asking "Are you
still there"; the purpose of this mechanism is to trigger the clearing of the user session
and related sessionStorage.
The latter item, localStorage, is used to store items that need to be persisted to the
users' local machine after they have closed the application and session. An example of
this would be application configurations such as time zone, which is useful and valid data
for more than a single session. In general, the types of data saved into localStorage
using the Web Storage API could just as easily be saved to a centralized application
database stored in the cloud. Two important cases that arise which could make using
localStorage preferred are for privacy-focused applications and applications that are
designed to run offline or without central control.
NOTE At the time of writing, the Chrome, Firefox, and Safari web browsers limit the total size of
data that can be stored using localStorage to 5MB. This is a shared limit that is applied to all
websites and applications sharing a domain name or IP address.
When designing privacy-focused applications, one way to provide guarantees for privacy
is to process and handle user data on-device. Handling user data on-device also carries
a secondary benefit of limiting the potential for unintended mixing or exposure of user
data in ways that could violate regulations such as the General Data Protection
Regulation (GDPR) in the European Union or the Personal Information Protection Law
(PIPL) in China. Code Radio sits within the second category of applications suitable for
localStorage because it is an application designed to operate without a central control. In
Code Radios' case, you are using it specifically to store the user’s choice of application
theme—specifically, whether a new dark theme is enabled or not.
code_radio % http-server
Starting up http-server, serving ./
http-server settings: #1
CORS: disabled
Cache: 3600 seconds
Connection Timeout: 120 seconds
Directory Listings: visible
AutoIndex: visible
Serve GZIP Files: false
Serve Brotli Files: false
Default File Extension: none
The resulting output returns the IP address and port that is assigned.
Available on:
http://127.0.0.1:8080 #1
http://192.168.125.169:8080 #1
Hit CTRL-C to stop the server
In a browser, navigate to 127.0.0.1:8080 to view the revised interface of the Code Radio
application. As a starting point, the HTML and CSS have been updated so that now the
application user interface displays a theme toggle in the upper left corner (figure 4.1).
The new theme toggle that is added will be used to allow a user to switch between light
and dark themes within the Code Radio application.
Figure 4.1. As a starting point, the Code Radio user interface has been provided. At this stage Code Radio
is a static web page without functionality. Additional Javascript is required to turn this web page into a
functioning application.
At this point, the theme toggle is non-functional; as you still need to implement the
underlying Javascript logic. You will add this additional programming in the following
section.
setItem()
getItem()
removeItem()
clear()
key()
You can add data to the localStorage through the setItem() method, which takes two
arguments in the following format:
localStorage.setItem("<key>", "<value>");
You can query existing data through the getItem() method, which takes a single
argument in the following format:
localStorage.getItem("<key>");
You can remove a single item from localStorage through the removeItem() method,
which takes a single argument in the following format:
localStorage.removeItem("<key>");
You can remove all items from localStorage through the clear() method, which does not
require any arguments:
localStorage.clear();
Last, in cases where iterating or looping through all entries in localStorage is desired,
you can use the key() method, which takes a single argument in the following format:
localStorage.key(<index>);
CAUTION
localStorage does not require or support authentication for access. For this reason,
sensitive data should never be stored using localStorage. For additional detail on
the localStorage specification and implementation, visit the Mozilla Developer
Network documentation page at developer.mozilla.org/e n-US/docs/
Learn/JavaScript/Client-side_web_APIs/Client-side_storage.
To implement localStorage within Code Radio, start by creating a new Javascript file
named darkmode.js within the directory web3-application-
architecture/chapter_4/chapter_4_begin/code_radio/js/.
Similar to the process used in the previous chapter to change the Code Radio button
font, toggling between the dark and light theme within Code Radio can be done by
toggling the application of a new "dark-theme" CSS class, which darkens the appearance
of elements within the Code Radio UI. Within the new darkmode.js file, a new function
named "toggle_theme" must be added to execute this. Within the function, a local
variable can be used to set the theme to "light" or "dark" before that value is saved to
localStorage as shown in listing 4.2. Take a moment to add this completed code to your
darkmode.js file.
Listing 4.2. The toggle_theme function which darkens the Code Radio UI
function toggle_theme() {
document.body.classList.toggle("dark-theme");
Although the function to change the Code Radio theme between light and dark is in
place, it is not yet connected to or called from the physical toggle button that is within
the Code Radio UI. To connect these two, a small amount of additional Javascript code is
needed.
To connect the new button UI to your Javascript file, a new variable named btn is
required, which selects the UI theme toggle within the HTML and stores it within the
Javascript. To achieve this, the Javascript built-in document.querySelector() method is
used. This method is required for this specific case because you are targeting a CSS
class that applies to multiple items which are changed when the dark theme is applied.
This is in place of document.getElementById() which you used in the previous chapter to
select a single item (the text on a single channel button) identified by an HTML id.
The new btn variable can be declared by adding the following line of code to your
darkmode.js file:
Now that a variable is defined that selects the UI theme toggle within the Code Radio UI,
as you did in the previous chapter, add a Javascript event listener to your darkmode.js
file, which triggers the toggle_theme() function during a click event:
btn.addEventListener("click", toggle_theme);
With the theme toggle and corresponding Javascript function now linked and saving to
localStorage, Code Radio can read the theme that is set in localStorage when it is
launched. To complete this functionality, add an additional variable named
"currentTheme" to darkmode.js, which reads the set theme from localStorage as well as
an if statement that adds the "dark-theme" CSS class if the theme has been set to
"dark." When completed, the additional code block should match listing 4.3.
if (currentTheme == "dark") {
document.body.classList.add("dark-theme");
}
For security, data saved to localStorage is scoped to the domain. This means that a
localStorage key:value pair stored for web3-application-1 cannot be read by web3-
application-2. The isolation also allows keys such as "username" to be used within many
Web3 applications without conflict. Further, because localStorage is a browser API, this
means that the actual file that is written to the user’s hard disk is written and managed
by the browser running your Web3 application. Both Chrome and Firefox browsers save
this data into an SQLite database as part of their application directories.
NOTE If the user removes or reinstalls the browser on their local machine, any data saved using
localStorage is removed as part of the browser.
After implementing this section’s code within the darkmode.js file, the Code Radio
application can be run again by issuing the "http-server" command and returning it to the
web browser. Your application should now be able to toggle between light and dark
themes while saving the chosen setting to your local machine. To test your
implementation of localStorage, try setting the Code Radio application to the dark theme.
After this, close the application and reopen it. The Code Radio application should now
directly load with the dark theme.
If for any reason, your local version of Code Radio does not load or toggle between
themes properly, you can also view the values being stored within localStorage from
within the developer tools of your browser GUI. Using the Chrome browser as an
example, navigating to the "Application" section will display a "Storage" section that
includes localStorage. Expanding the localStorage category will display all configured
key:value pairs. These key:value pairs will also update as you toggle the theme within
the Code Radio UI as shown in figure 4.2.
Figure 4.2. Using the browser built-in developer tools allows viewing and debugging the storage used
within your Web3 application.
Beyond the theme setting that can be saved as a simple key:value data structure, the
Code Radio application also handles playback of music files, which cannot be stored
client-side using localStorage. For more complex data, such as music and video files, the
IndexedDB API can be used.
NOTE When designing your Web3 application, be sure to confirm your application data model
matches the specific IndexedDB disk limits set by the browsers you intend to support.
IndexedDB is popular for storing complex data client-side, in part because it is a native
part of the HTML5 standard, allowing it to receive broad browser support. More bespoke
alternatives to IndexedDB are also available to add further capabilities, including RxDB
which uses a NoSQL model on top of IndexedDB, and PouchDB , which implements
CouchDB client-side, on top of IndexedDB. IndexedDB can be very powerful, and as a
result, also complicated. This section provides a general introduction to IndexedDB,
before IndexedDB support is added to Code Radio later in the chapter.
Although IndexedDB doesn’t support the SQL language, its usage workflow may be
familiar for developers who have interacted with database libraries and tools such as
SQLite or SQLAlchemy. At a high level, the usage pattern recommended with IndexedDB
can be understood as five steps:
The following sections expand and provide examples for each of the workflow steps.
NOTE The request object is part of the Javascript Fetch API. If you are unfamiliar with the Fetch
API, additional information can be found at the Mozilla Developer Network website: developer.
mozilla.org/en-US/docs/Web/API/Fetch_A PI.
When using the IndexedDB open() method, if a database does not exist, IndexedDB will
create it. Two arguments are required for the open() method, the first is the database
name and the second is the version number, as shown in the following example:
NOTE IndexedDB does not support floats such as "2.7" for version numbers, a whole number is
required. If a float value is used for the version number, IndexedDB will round down to the closest
integer value; "2" in this case.
let db;
When using IndexedDB in an application, the user is prompted to confirm whether they
would like to allow the application to use local storage. A user denying the use of storage
is the most common error generated by IndexedDB. Notice that in the previous example,
there is a new variable declared named db. In the case of a successful database
connection, the success event saves the result of that connection request in the new
variable db, so that it can be accessed and referenced elsewhere in the Javascript code.
NOTE Similar to standalone databases, IndexedDB also supports creating indexes of object stores
so that other parts of records can be searched. For example, in the previous example, when
creating a new user, your application may also collect "first name" and "company" values. In this
case, an index can be created for company, which would then allow searching for users based on
either email, company, or both. Additional information for creating and managing indexes with
IndexedDB can be found by visiting the Mozilla Developer Network IndexedDB reference
documentation at: developer.m ozilla.org/en-US/docs/Web/API/IndexedDB_A PI/Using_
IndexedDB#creating_and_structuring_t he_store
Figure 4.3. A representation of the relationship between IndexedDB, databases, and object stores.
Now that your IndexedDB object store has been created, you can perform CRUD
operations to both interact and build atop the IndexedDB storage.
NOTE Improve the performance of your application by using the readonly permission whenever
possible. Multiple readonly operations can be performed in parallel on an IndexedDB object store,
while readwrite operations place a lock on the object store for transaction safety and are
performed serially.
The following sections step through IndexedDB CRUD operations one at a time.
const customers = [
{ email: "jonathan@company.com", company: "The Billions Corporation"},
{ email: "ignacio@charity.org", company: "Local Charity A"},
{ email: "gizem@university.edu", company: "Bootcamp University"}
];
Using the same error handling format as before, the customer data can be iterated over
and added to the IndexedDB object store using the Javascript built-in forEach callback
function as shown in 4.7.
Listing 4.7. Iterating over the customer object to add new user records.
Because IndexedDB handled transactions using Javascript events, the event result can
be stored for later use. In this case, the result that is returned is the defined keyPath or
primary key. Listing 4.8 shows an example of this using the customers.foreach()
function.
Listing 4.8. Iterating over the customer object to add new user records.
After populating the database with data, the stored records can be queried, parsed, and
used throughput your Web3 application.
Here the request result is not saved to an intermediate variable but is instead printed
directly to the console. If further processing will be done, such as updating stored
values, storing the request result into an intermediate variable is usually required and is
covered in the following section.
Notice that the permissions defined for the transaction have also been updated from
readonly to readwrite, this is required for the put function to be able to write the record
back into the object store after it has been changed.
With the ability to edit records in place, a final resource in managing the lifecycle of
application data is the ability to fully delete records.
If for any reason, IndexedDB CRUD operations are not behaving as expected. Using your
browser’s built-in developer tools can assist in debugging here as well. Using the Chrome
browser as an example; in the same manner that localStorage data was viewed earlier
in the chapter, you can view objects stored in IndexedDB by accessing developer tools
and navigating to the "Application" tab. Within this tab, IndexedDB storage can be
browsed as test CRUD operations.
With a firm grasp of creating an IndexedDB instance and completing CRUD
transactions against the database, one additional consideration to be aware of is
IndexedDBs lack of ACID compliance.
IndexedDB supports atomic transactions but has a specific limitation that occurs when
a user unexpectedly closes the browser. Transactions that take a long time to complete
and are done in the background may be interrupted and reverted if the user
unexpectedly closes the browser. When designing your application to make use of client-
side storage using IndexedDB, it is important to be aware of this behavior. One way to
compensate for long-running operations is to notify the user directly. You may have
experienced this from existing applications, with messages similar to "updating… please
do not close or refresh the browser until updating is complete."
To deliver consistency, a database must enforce that all transactions committed to the
database are processed the same. IndexedDB guarantees consistency and uses a model
of strict serializability , which guarantees that operations are strictly handled in the order
that is defined in the transaction. It does not try to compensate, do error checking,
autocomplete, or otherwise compensate the order of operations within transactions.
Isolation in an ACID-compliant database guarantees that no two transactions can be
performed on the same resource. For example, if a store has five units of an item, and
six customers try to buy that item at the same time, isolation ensures that these orders
are completed one at a time, to correctly validate that the item is sold out after five
transactions. IndexedDB supports transaction isolation by limiting write access to object
stores to a single transaction at a time. When designing your Web3 applications, keep in
mind that read operations can happen in parallel and have no such limitation or locked
access. Only use readwrite transactions when necessary and not as a default.
IndexedDB does not support durability features such as replicas and fault tolerance.
Moreover, the stores of physical data within IndexedDB are part of the browser
application install. If the user of your Web3 application installs the application using
Firefox, IndexedDB places the data within a subdirectory of the Firefox application on the
user’s disk. If the user accesses your application from another browser, the state, and
data that was stored in IndexedDB within Firefox will not be accessible. This behavior is
the same as with browser cookies and caches but may be unexpected behavior for the
user, depending on the design of your application.
NOTE One way to ensure that your Web3 application is always used with the same browser
instance is to distribute it as a browser-specific extension. Another solution to compensate for a
lack of durability in client-side storage is to place portions of your application data within a
blockchain. Both of these items are topics covered in dedicated chapters later in this book.
Because Code Radio does not use many features of IndexedDB, completing an overview
of IndexedDBs' most commonly used features provides the baseline context to use
IndexedDB more widely and apply it to your own Web3 application ideas. The next
section narrows the scope of IndexedDB to only the features required to store the Code
Radio music files client-side and includes a few new considerations that are specific to
the Code Radio application architecture.
Figure 4.4. A comparison of user-uploaded files (left) and files bundled within Web3 applications (right).
With the programming completed in the previous chapter, Code Radio was configured to
use audio sources that stream media from a web-addressable URL. In order to store
Code Radios music files in IndexedDB, they must first be converted into a blob (binary
large object) format that can be passed into IndexedDB. The standard method of doing
this is using the Javascript File API . Using the Javascript File API allows the user to
choose a file for input. The File API then supports reading this input as an array buffer,
binary string, or as text that is compatible with IndexedDB.
An important distinction in this scenario is that the user is required to make a file
selection for this processing to happen. This workflow is restricted for improved security.
Imagine a scenario where loading a website or Web3 application allowed full access to
files on a user’s device! We definitely don’t want that. A key part of Code Radios
architecture is that it explicitly does not support user-generated content. To avoid illegal
file sharing, there is no mechanism for user file uploads, and by extension, no access to
the File API. Because of this design, Code Radios' music files must be encoded manually
and placed into IndexedDB.
NOTE IndexedDB relies on the structured clone algorithm built into Javascript to move files into
storage. The structured clone algorithm can support the storage of Javascript strings, arrays, sets,
array buffers, and more as well as Web API types such as blobs. For a full list of files types
supported by the structured clone algorithm and IndexedDB, visit developer.m ozilla.org/en-
US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm.
Encoding the Code Radio music files involves the following steps:
Conversion of the .wav-formatted music files into base64 format.
Conversion of the base64 encoded media into a binary blob that is
compatible with IndexedDB.
Because manual file encoding is an outlier process, instructions and code for this step
have been placed in Appendix B. These instructions can be used both with Code Radio
and as a reference for future Web3 applications.
Before beginning the encoding steps, return to the programming exercise files for this
book. Starting at directory web3-application-
architecture/chapter_4/chapter_4_begin/code_radio/ add a new file to the js directory,
named "indexedDB.js". Within this file, all Javascript related to Code Radios' initial
IndexedDB configuration will be placed. As a simplification for now, Code Radio will use a
single .wav-formatted music file; this file is named "trap and ink" and is located in the
directory web3- application-architecture/chapter_4/chapter_4_begin/code_radio/media/.
Now, take a moment to complete the manual encoding steps provided in Appendix B.
At the end of this step, you will have a new audio source named "audioURL.src" which
points to a blob URL that can be stored in IndexedDB.
Listing 4.12. Storing your encoded wav music files within IndexedDB.
const musicFiles = [{ #1
name: "trap and ink",
url: blobUrl,
sourceData: blob
}];
const dbName = "music_database";
const request = indexedDB.open(dbName, 1); #2
At this moment, you can save and close the indexedDB.js source file. The file now
contains all programming required to handle the conversion and storage of the original
wav-formatted music files used by Code Radio. For convenience, the Code Radio HTML
page has already been updated to include and load the Javascript file name
"indexedDB.js".
By loading this source file with the Code Radio UI, if the IndexedDB and object store
instance do not already exist, they will be created and the blob containing the Code
Radio music data will be saved to the user’s local disk. You are able to confirm that this
occurs by issuing the http-server command to serve the Code Radio instance and
returning it to your browser. Before navigating to the Code Radio URL, open the
developer tools of your browser and navigate to the "Application" menu. This menu
contains a "Storage" section, which lists all storage options available, including
IndexedDB.
With the developer tools menu open, navigating to the Code Radio URL shows the
IndexedDB instance created and populated in real-time as shown in figure 4.5. WebSQL
may also be shown as a storage option within your browser; note that this method of
storage has been deprecated in favor of storage using IndexedDB.
Figure 4.5. The application menu within the browser developer tools allows viewing the IndexedDB
instance, object stores, as well as the data that has been stored by the Code Radio application.
In the next section, the second half of Code Radio’s IndexedDB programming is added to
handle the reading and playback of the music files that were just stored.
Listing 4.13. Defining the database and object store to use for Code Radio music files.
Next, add a Javascript function named getData() that handles reading music files and
updating the audioURL source that was defined in the previous section. The function in
this case is not specific to a station but is generic, taking as input the name of the song
that is requested for playback. There is also no return value for this function; instead,
only the audioURL source is switched.
Remember that when the IndexedDB object store was created in the previous section,
"name" (the song name) was set as the keyPath and is therefore unique. The structure
of your Javascript function will follow the format presented earlier in the chapter for read
transactions using IndexedDB. A completed implementation of the function is shown in
listing 4.14.
function getData(song) {
const transaction = db.transaction([object_store]);
const objectStore = transaction.objectStore(object_store);
const objectStoreRequest = objectStore.get(song);
To reconfigure the streams that are started by clicking the Code Radio button within the
UI, the three functions that are connected to the click event listeners of the UI must be
updated so that they call the read function of the IndexedDB database, to read the music
files that are now stored locally, rather than in the cloud. Start this second step by
commenting out, or removing the previously defined stream sources. For convenience,
these original audio sources are reproduced in listing 4.15.
Listing 4.15. Commenting out the original Code Radio audio sources.
Listing 4.16. Commenting out the logic connecting to the retired radio streams.
// classic_stream.loop = true;
// ambient_stream.pause();
// ambient_stream.currentTime = 0;
// lofi_stream.pause();
// lofi_stream.currentTime = 0;
At this time, all references to the previous audio sources have been removed and the
new sources within IndexedDB client-side storage can be added. Within each of the three
playback functions, add the logic to open a connection to IndexedDB as the first action.
If successful, this connection must call the previously defined getData() function, with the
name of the song that should be played as shown in listing 4.17.
Listing 4.17. Calling the getData function to read the "trap and ink" music file from IndexedDB.
As a final refactoring, the play(), pause(), and currentTime functions and method can be
again added at their original locations, but this time, using the audioURL inside the
IndexedDB record as the playback source. Because you are no longer using separate
audio sources and instead are switching the audioURL pointing to a single source
(IndexedDB), it is no longer required to have the duplicate play(), pause(), and
currentTime functions and method for each Code Radio station. The updated and
condensed version for the play_classic() function is shown in listing 4.18.
Listing 4.18. Updated logic to switch song playback while using IndexedDB.
function play_classic() {
const DBOpenRequest = window.indexedDB.open(database, 1);
DBOpenRequest.onsuccess = (event) => { #1
db = DBOpenRequest.result;
getData("trap and ink");
};
audioURL.pause(); #2
audioURL.currentTime = 0; #2
if (!classic_playing){
audioURL.play(); #2
classic_playing = true;
document.getElementById("btn-classic").classList.add("playing");
document.getElementById("btn-ambient").classList.remove("playing");
document.getElementById("btn-lofi").classList.remove("playing");
} else if (classic_playing){
classic_playing = false;
document.getElementById("btn-classic").classList.remove("playing");
} else {pass;}
}
Using the capabilities of IndexedDB, Code Radio now stores music files on the client
machine. With this in place, both the application settings and music file are maintained
client-side which fully removes the need for a centralized cloud-hosted database.
Depending on the design of your Web3 application, bundling files in the way shown in this
chapter can become quite large and difficult to load. One way to compensate for this is
to bundle only a minimum of content with your Web3 application and rely on peer-to-
peer connections to other users of the application to gradually download and sync the
application’s full feature.
Adding resource discovery and spreading the load of your Web3 application in this
way opens a path to create more feature-rich and resource-intense Web3 applications.
In the case of Code Radio, this peer-to-peer connectivity unlocks the final feature of its
application architecture required to allow it to fit the definition of Web3. Adding this peer-
to-peer programming to Code Radio is covered in the next chapter.
4.5 Summary
Persistent client-side storage in Javascript can be handled using
localStorage and IndexedDB.
localStorage is capable of handling simple key:value pairs and is
limited to 5MB of storage per application.
IndexedDB supports complex file storage with significantly higher
storage quotas that are determined by the users' local machine
capacity.
Intermediate file processing may be required to store complex files
using IndexedDB.
5
Using Libp2p to connect
applications peer-to-peer
In this chapter you will learn about the Libp2p Javascript library, and how to use it to
allow your Web3 applications to communicate peer-to-peer. After replacing centralized
storage and compute with client-side Javascript programming in previous chapters,
allowing instances of the Code Radio demo application to communicate peer-to-peer is
the final step in removing all centralized resources and delivering a product that can be
certified as Web3.
As a networking stack, Libp2p supports a wide range of features that support different
network functions. A basic knowledge of data networking is most useful at the beginning,
when deciding if Libp2p can or should be used. A partial list of features supported by
Libp2p include the following:
Figure 5.1. Before data can be sent between application instances, a certain amount of overhead is used
to establish the connection and negotiation options such as encryption. By using a single stream
connection, Libp2p is able to avoid this overhead when sending multiple requests.
NOTE Network programming is an entire domain of expertise in itself and is not exhaustively
covered in this book. For a broader introduction to data-networking concepts, I recommend you
read "Learn Cisco Network Administration in a Month of Lunches" by Ben Piper, www.manning.
com/books/learn-cisco-network-administration-in-a-month-of-lunches.
It should be noted that because Libp2p has multiple implementations, there may not be
feature parity among the different implementations. As an open-source project, Libp2p
also receives regular updates, so the previous feature list should be taken as a sampling
of Libp2p’s capabilities, rather than an exhaustive list.
NOTE The Secure Contexts web standard is a proposal of the World Wide Web Consortium (W3C).
A complete version of the standard, maintained by the W3C, can be found online at: w3c.github.
io/webappsec-secure-contexts/.
Although full peer-to-peer operations are not permitted exclusively in the browser, it is
possible to use a parent connection that was established through a secure context, and
reuse it to send later communications peer-to-peer. This is how applications such as
video conferencing systems are able to stream content between participants. The
following section provides further detail of the WebRTC protocol capabilities, which are
inherited by Libp2p.
5.3.1 WebRTC
WebRTC is best known as the protocol that enables video calling for applications such as
Zoom and Google Meet. Connections that are made using WebRTC, whether for video
calling or otherwise, rely on a signaling server during communication to resolve peer
addresses. When signaling servers are centrally managed by a single entity (such as
with Zoom and Google Meet), WebRTC is considered a client-server model, rather than
full peer-to-peer. In the video call example, two browsers that join a single video call,
exchange signaling messages with a WebRTC server, which informs the browsers where
they are to send video data. Once the browsers have each other’s address, the video
portion of the call is sent using a data channel that is peer-to-peer (figure 5.2). WebRTC
is a W3C internet standard and pre-dates the secure contexts standard. For this reason
web browsers permit WebRTC to establish communication using self-signed certificates of
identity to support encryption of communication between peers.
Figure 5.2. A WebRTC implementation which relies on centrally-managed signalling servers is considered
a client-server architecture, rather than peer-to-peer.
Libp2p as a protocol, supports establishing connections using WebRTC; when doing so, it
is bound by the underlying client-server mechanics of the WebRTC protocol. This means
that when using Libp2p to communicate browser-to-browser over WebRTC, the initial
connection signaling must still occur with a WebRTC server. In Web3 application
architecture, the role of the WebRTC server can be filled by multiple independently-
managed Libp2p standalone nodes to allow the application to operate fully peer-to-peer
(figure 5.3).
Figure 5.3. Libp2p supports making connections using WebRTC. In such cases, the role of WebRTC server
can be filled by multiple independently-operated Libp2p standalone nodes to allow the application to
continue to operate fully peer-to-peer.
Beyond WebRTC, Libp2p also supports newer WebSocket (used in conjunction with TCP)
and WebTransport (used in connection with QUIC) connections. In both of these cases, a
certificate from a recognized certificate authority is required to validate the domain used
by the application. Because an authorized certificate authority would introduce a level of
centralization, and because most instances of peer-to-peer applications will not have a
unique domain name assigned, using WebSocket and WebTransport connectivity with
Libp2p is not recommended at this time.
Libp2p handles hole punching by using relays. A Libp2p relay is an instance of Libp2p
that is publicly reachable over the internet. For Web3 applications that rely on Libp2p,
having at least one instance of the application reachable over the public internet allows
instances that are behind a firewall to relay their communication to other instances by
going through the public node. Typically, only the initial connection signaling uses the
relay connection, with all traffic after this being able to travel peer-to-peer. In this
format, hole punching behaves similar to WebRTC as shown in figure 5.4, with additional
capabilities such as network address translation (NAT) to enable locating endpoints that
have their IP address changed by a firewall.
Figure 5.4. Libp2p standalone nodes support hole punching, which behaves in a manner similar to the
WebRTC protocol with additional capabilities such as NAT to support peer-to-peer connectivity among
applications run behind a network firewall.
NOTE For additional detail on hole punching and the role of NAT in peer-to-peer communications
read the paper "Peer-to-Peer Communication Across Network Address Translators" from the
Massachusets Institute of Technology: pdos.csail.m it.edu/papers/p2pnat.pdf
Before progressing to implement Libp2p within Code Radio, the following section
showcases an example Libp2p configuration of a network ping to help support the
connectivity information that was just introduced.
Installing these NPM packages will place several dependency files within the project
folder. Because you are using Javascript, compiling is not required. This is important
because once these dependency files have been downloaded to your local hard drive, as
long as the licensing of the code allows it, they can be copy/pasted or moved with the
rest of your project code, to allow removing the NPM registry as a point of centralization
going forward. In the next section, you begin programming the ping send and receive
functions, which will reference the NPM packages and files that were just installed.
The next step in configuring Libp2p to send ping messages is to let it know which address
it should use to communicate. As in the previous section, this is done by including it as
part of the configuration object. Libp2p can be set to use a single address or multiple.
Libp2p addresses are defined using the following format:
If you are happy to use any available port, this can be achieved by setting the port
number to 0. It is also important to note that the protocol that is defined in the address
must match one of the available transports that have been set. In the previous example,
the transport is was defined as TCP. Listing 5.2 shows the updated configuration with a
new Libp2p address defined.
Libp2p is now ready to be run and should be started using a new async function named
startLibp2p. An async function allows Javascript to continue processing a script, without
waiting for the function to complete. Because Libp2p typically will run as long as your
Web3 application is open, you do not want Libp2p to prevent other portions of Javascript
from running within your Web3 application. This is a change from the direct function calls
you used in previous chapters. After creating a startLibp2p async function, within the
function, Libp2p can be started using the .start() method on the Libp2p instance.
Querying the address that Libp2p uses is done using the getMultiaddrs() method on the
Libp2p instance. The completed startLibp2p async function, which both starts Libp2p and
prints the Libp2p address as a console log, is shown in listing 5.3.
await libp2pInstance.start() #2
console.log('Libp2p is listening on addresses:')
libp2pInstance.getMultiaddrs().forEach((addr) => {
console.log(addr.toString()) #3
})
}
startLibp2p()
If you have not done so already, take a moment to update your libp2p_ping.js file so
that it matches the code shown in listing 5.3. You can test that your new configuration is
Libp2p works as expected by running your Javascript file using Node.js:
node libp2p_ping.js
When starting Libp2p for the first time, a unique encryption key is automatically
generated to encrypt Libp2ps' connections. As part of negotiating connections with other
Libp2p instances, that unique key is also used to derive a unique peer ID, which Libp2p
uses in conjunction with the address you have set. If your instance of Libp2p starts
successfully, the address that is returned by the getMultiaddrs() method will be in the
format of the following:
listening on addresses:
/ip4/127.0.0.1/tcp/50438/p2p/sKJHkljhuGHfd... #1
NOTE For more information about the processes and encryption behind how Libp2p generates
peer IDs, visit the Libp2p peer ID specification page at github.com/libp2p/specs/blob/master/
peer-ids/peer-ids.m d.
After validating your Libp2p instance is able to start and listen for network connections,
you can add the second half of the code, which will send messages between two
instances of Libp2p running no your local machine.
With the additional import statement included, you can now add additional programming
to allow your Libp2p instance to receive messages from peers. This additional
programming is being added into the same file so that a single execution of
libp2p_ping.js creates an instance of Libp2p with the capabilities to both send and
receive.
NOTE Javascript as a programming language can run entirely within a web browser. Node.js on
the other hand is a server-side runtime for Javascript that runs outside a browser. By running
outside the browser, Node.js can access operating resources in ways not allowed from the
browser sandbox. In this section, Node.js is being used only for the purpose of running multiple
instances of the libp2p_ping.js file and is not required otherwise. Removing the node:process
module import statement would allow Code Radio and other similarly designed Web3
applications to run wholly within the browser, without external dependencies such as a Node.js
installation.
if (process.argv.length >= 3) {
const ma = multiaddr(process.argv[2]) #1
console.log(`pinging remote peer at ${process.argv[2]}`)
const latency = await libp2pInstance.ping(ma) #2
console.log(`pinged ${process.argv[2]} in ${latency}ms`)
} else { #3
console.log('no remote peer address given, skipping ping')
}
node libp2p_ping.js
The initial execution of the code, although not communicating with another instance of
Libp2p yet, prints its peer ID to the console, as seen earlier. Once arriving to the if
statement that was added in the previous section, the new Libp2p instance skips the ping
action as shown in listing 5.7. The Libp2p instance continues to operate and listen on it
programmed TCP port, event though there is no explicit action it has been programmed
to perform.
listening on addresses:
/ip4/127.0.0.1/tcp/51324/p2p/KJghjkhgJhgJ... #1
no remote peer address given, skipping ping
In a new terminal window, the Libp2p peer ID tat was printed to the console from the
first instance, can now be used when launching the second instance, as shown in the
following example:
Launching the additional instance of libp2p_ping.js with the peer ID will allow the second
instance to initiate a network ping request to the peer ID that has been provided in order
to print a latency value to the console as shown in listing 5.8
You have successfully instantiated two Libp2p nodes with the ability to send network
communication to each other. In the next section this working peer-to-peer
communication will be extended and implemented within Code Radio to allow sharing of
its music files peer-to-peer. If you have not terminated them, it is safe to leave your
active instances of Libp2p running, as the peer ids that have been output in this section
will be used later in this chapter when updating Code Radio.
Figure 5.5. A Code Radio Web2 application architecture using centrally-managed infrastructure within
Amazon AWS.
Figure 5.6. For Web3, infrastructure that previously lived within a centrally-managed cloud provider, can
now be replaced with Javascript programming that runs client-side.
As a final stop in showing the progression of Code Radios' Web3 architecture, figure 5.7
shows a revision of a second diagram that was first presented in chapter 2, to reflect the
new peer-to-peer nature of the application, in which every participant running the
application also serves as a portion of its infrastructure.
Figure 5.7. Building an application that can run entirely client-side is a good foundation for adding in
interactions which operate peer-to-peer.
The following sections will now detail the programming of that final step of replacing
Code Radios' centralized domain registration with connectivity supported by peer ids
provided through Libp2p.
5.6.1 Starting a new network using seed lists and Libp2p bootstrap
Now that you have demonstrated working communication by manually connecting
instances of Libp2p, it is now time to configure Libp2p for automatic peer discovery. This
automatic peer discovery is the configuration that will be used with Code Radio. If you
have terminated the previously running instances of libp2p_ping.js from earlier in this
chapter, take a moment to again run two of those instances, so that they print their
chosen peer ID to the local console.
With two instances of Libp2p running from using the libp2p_ping.js file, Code Radio
can be updated to use the bootstrap module of Libp2p. A detailed explanation of the role
of bootstrapping in Web3 systems is outlined in chapter 2 of this book, but as a review,
bootstrapping refers to the process of bringing a peer-to-peer network online for the first
time. When a Web3 application is first launched, there are not yet any peers to connect
to. In this unique scenario, a bootstrap configuration provides a list of peers to connect
to when the application starts. This initial list of peers is also sometimes referred to as a
seed list. In his section you will practice the bootstrap configuration and seed list concept
by applying it to Code Radio.
To add the bootstrap configuration to Code Radio, start by creating a new file named
libp2p_node.js (available within this books code repository at directory
/chapter_5/chapter_5_begin/code_radio). At the top of this new Javascript file, the
dependencies must be added as import statements. In addition to previously used import
statements, a new dependency for a bootstrap module is included. Add the important
statements shown in listing 5.9 to the top of your libp2p_node.js file. Again, note that the
Node.js process" module is being imported to allow running more than one instance of
Libp2p on your local machine.
With import statements for all dependencies in place, the remainder of the Libp2p
instance configuration reuses portions of the code that you have seen in the Libp2p ping
example with slight modifications. The first modification is the inclusion of a new list of
Libp2p peer ids(seed list) that the application can use to communicate. These addresses
are defined as an array variable named bootstrapMultiaddrs. Take a moment to add this
new array variable to your libp2p_node.js file. Within the array, add the Libp2p peer ids
that were printed to the console earlier in the chapter when running your libp2p_ping.js
files. Note that these earlier instances should still be running on your local machine to
enable communication. An example of the completed bootstrapMultiaddrs array variable
can be seen in listing 5.10.
Listing 5.10. Adding a list of Libp2p peer ids to Code Radio for application bootstrapping.
const bootstrapMultiaddrs = [ #1
'/ip4/127.0.0.1/tcp/50924/p2p/12D3KooWJ3zFDFr...',
'/ip4/127.0.0.1/tcp/61884/p2p/12D3KooWHdzWnJp...'
]
The next step is to define the configuration for the instance of Libp2p that will be used by
Code Radio. As with the Libp2p ping example, you can do this by assigning the
libp2pInstance variable to the object being returned by the createLibp2p function. Within
this call to createLibp2p, you will include configurations for the listening address, which
will be your local machine while testing, as well as the transport, encryption, and
multiplexer configurations. A completed version of the Libp2p instance configuration is
shown in listing 5.11. For simplicity, the instance variable is named libp2pInstance. Take
a moment to add this portion of code to your Javascript file libp2p_node.js.
Listing 5.11. Creating the instance of Libp2p to be use dby Code Radio.
To make use of the bootstrap functionality to automatically connect to Libp2p peers, the
variable that is creating an instance of Libp2p will also need a few lines of additional
configuration that was not included in the earlier ping example. These additional
configuration lines do two things:
1. The first is to tell the instance of Libp2p to use the list of peer ids you
defined earlier within the bootstrapMultiaddrs variable; this is done
using a new peerDiscovery parameter.
2. The second is to configure Libp2p to autodial or attempt to
automatically connect to this list of peers using a new
connectionManager parameter.
You can see these additional parameters in listing 5.12. Before proceeding, take a
moment to add the additional configuration to your Javascript file, libp2p_node.js.
Once more, as done in the Libp2p ping example, now add the portion of Javascript code
which starts your instance of Libp2p and prints its unique peer ID value to the console as
shown in listing 5.13.
await libp2pInstance.start() #1
console.log('libp2p has started')
console.log('listening on addresses:')
libp2pInstance.getMultiaddrs().forEach((addr) => {
console.log(addr.toString()) #1
})
The following step is not required for your instance of Libp2p to function, but is added to
assist in debugging your Libp2p instance in the event that an error occurs. Take a
moment to add two Javascript event listeners to your libp2p_node.js file. These event
listeners will listen for peer:discovery and peer:connect events and log these details to
the console. The completed configuration for this logging is shown in listing 5.14.
Your libp2p_node.js files now has all functionality required to start, discover new peers,
and automatically connect. When Libp2p discovers a peer, it saves this information
locally in something called a peer store . As part of keeping this peer store up-to-date,
the Libp2p protocol checks its connections to its peers periodically to ensure the peer is
still available. Also as part of these recurring checks, instances of Libp2p also notify
peers when new peers are discovered.
This means that as new application instances connect, they discover further peers
beyond those originally defined in the bootstrap seed list. In the case of Code Radio,
once you have programmed the configuration as described in this chapter and a few
additional users run the application, it becomes possible to fully remove the original peer
ids that are included in the seed list. This removal can be done without any impact on the
connectivity for the users who are already running the application, provided they have
learned about each other. New users who have never run the Code Radio application
however, would still be reliant on the initial seed list. The following section demonstrates
testing the seed list and peer discovery process.
node libp2p_node.js
As with previous instances, your Javascript code will run and print its new peer ID to the
console as shown in listing 5.15.
listening on addresses:
/ip4/127.0.0.1/tcp/50438/p2p/8sgE2jqq9G... #1
Shortly after Libp2p generates its peer ID and begins listening on an open TCP port, it
progresses to reading from the configured seed list and attempts to establish peer
connections. If successful, these items trigger the respective Javascript event listeners to
print to the console the peer addresses it has learned, followed by confirmations of
successfully connecting to these peers, as shown in listing 5.16.
Discovered /ip4/127.0.0.1/tcp/50438/p2p/sKJHkljhu... #1
Discovered /ip4/127.0.0.1/tcp/50439/p2p/3kjhHG2nk...
Connected to /ip4/127.0.0.1/tcp/50438/p2p/sKJHkljhu... #2
Connected to /ip4/127.0.0.1/tcp/50439/p2p/3kjhHG2nk...
Going one step further, with the existing Libp2p instances still running, start one more
instance of libp2p_node.js. As this instance comes online, it repeats the same sequence
seen with the first instance by initially printing its newly generated peer ID and then
progressing to read peer ids from the configured seed list before attempting to connect
to them. Once the additional instance has established connections to one or more other
instances of Libp2p, it will begin the previously described process of sharing information
from its peer store, to propagate information for all the connections that it knows to the
other instances of Libp2p it has connected to (figure 5.8).
Figure 5.8. Using libp2p, application instances learn of new participants automatically by asking their
peers periodically, who they have connections to. This helps information for discovering new participants
to spread among all users of the application.
As this process runs, your Libp2p instances will attempt to connect to the new peer ids
they learn, resulting in the updated Libp2p connections being printed to console as shown
in listing 5.17.
Discovered /ip4/127.0.0.1/tcp/50438/p2p/sKJHkljhu... #1
Discovered /ip4/127.0.0.1/tcp/50439/p2p/3kjhHG2nk...
Connected to /ip4/127.0.0.1/tcp/50438/p2p/sKJHkljhu...
Connected to /ip4/127.0.0.1/tcp/50439/p2p/3kjhHG2nk...
Discovered /ip4/127.0.0.1/tcp/50438/p2p/8sgE2jqq9G...
Connected to /ip4/127.0.0.1/tcp/50438/p2p/sKJHkljhu... #2
Connected to /ip4/127.0.0.1/tcp/50439/p2p/3kjhHG2nk...
Connected to /ip4/127.0.0.1/tcp/50438/p2p/8sgE2jqq9G...
All instances of Libp2p running from the libp2p_node.js file have now learned of each
other and will continue to share periodic updates until terminated. If one or more of your
Libp2p instances are terminated, you will see this reflected, as the printed console output
refreshes with updates. If your running instanced of Libp2p do not behave as shown in
the previous listing, take a final moment to compare your written code to the final
reference version of the code provided with this book’s code source files in the
repository directory /chapter_5/chapter_5_end/code_radio/libp2p_node.js.
5.7 Summary
Libp2p is a network protocol that allows applications to connect peer-to-
peer.
When using Libp2p, centralized application DNS names can be replaced
with Libp2p peer ids.
Libp2p instances can be placed into two categories: standalone and
browser.
Running Libp2p standalone supports the TCP and QUIP transport
protocols.
Running Libp2p in the browser allows support for the WebRTC,
WebSocket, and WebTransport transport protocols in conjunction with
TCP and QUIP.
Libp2p encrypts all network connections by default.
Multiplexing can allow multiple streams of communication to share a
single Libp2p connection.
The Node.js built-in node:process module can be used to run multiple
instances of Libp2p to test Web3 application communication.
Using the Libp2p bootstrap module allows Web3 applications to
automatically connect to as well as share the connection details of new
application participants.
6
Using Libp2p for content
distribution
In this chapter, you’ll learn about the publish/subscribe (pub/sub) features of Libp2p.
Using publish and subscribe, Web3 application developers allow instances of their
application to send and receive messages to each other instead of communicating via a
central server. This functionality replaces a central server and allows applications to stay
up to date by synchronizing settings and code changes to deliver rich media and other
content to users as they connect. To demonstrate this, you will update the Code Radio
demo application to allow application instances to synchronize the music catalog to
discover songs not in their local library and download those songs from peers. Within the
Code Radio application architecture, Libp2p pub/sub communications are part of the base
networking layer (figure 6.1).
Figure 6.1 Defining the Web3 application architecture of the Code Radio application starts at the base
network layer.
A pub/sub system that most people have interacted with is a chat room. Using the
example of a travel chat room, all participants in a travel chat room are subscribed to
the topic travel. Within the chat room, everyone can share or publish messages about
travel, which are then delivered to the room and everyone who has subscribed. By
publishing and subscribing in this way, everyone in the chat room can stay up-to-date.
Beyond the ability to share data between participants, using pub/sub mechanics in a
Web3 application can also have drawbacks. When using pub/sub to share data, some
design targets to keep in mind include the following:
Reliability: Can all messages reach all subscribers in a reasonable
amount of time? How does the application behave if a message is
received late or not at all?
Speed: Does your application rely on receiving messages to update
portions of the user interface? If pub/sub operations affect your
application interface, delays in messaging can cause the application to
appear unresponsive to the user.
Efficiency: How many messages does your application need to send,
and how often? Sending many messages at a high frequency, or
messages that are very large, may degrade performance when the
application is used over a slow connection.
Resilience: Can users join and leave your application without causing
service issues? Would your application perform the same if a large
portion of users were periodically offline? As a networking layer, Libp2p
dynamically handles changes in peer connectivity. A best practice in
Web3 design, is to avoid using statically assigned resources that could
become unavailable as participants join and leave your applications
network .
Scale: How many subscribers are expected for each topic? One way to
allow your Web3 application to scale when using pub/sub mechanics is
to assign each application features to a dedicated topic.
Simplicity: Across all topics and messages, is your application still
reasonable to maintain and debug? For each application feature, has
pub/sub messaging been reduced to a bare minimum? How are
messages exchanged to reach all application states? Can you easily re-
create all messages in isolation to debug and resolve application bugs?
The following section provides additional detail for how Libp2p handles network
communication and by extension, pub/sub communication underneath.
NOTE When using Libp2p, changing the peering degree higher or lower is one way to tune your
application’s performance. For example, setting the number of target connections to a higher
value can deliver higher increased reliability, because there is an increased chance that each
application instance will receive all possible messages. This change, however, also adds the
drawback of increasing the amount of data sent in the network, because all messages are being
sent to more peers. For the purposes of the Code Radio application presented in this book, the
peering degree is left at its default.
Figure 6.3 Once connected, full-message peers share a record of all of their active subscriptions.
When a Libp2p instance wants to publish a message for a given topic, it broadcasts that
message to all full-message peers it is connected to. This broadcast happens regardless
of whether the connected peer is subscribed to the topic and can make use of the
message. After full-message peers receive published messages, they in turn store a
copy of the message and broadcast that message to all of their connected full-message
peers. Through this process, messages that are published are able to propagate through
the entire network. Depending on how densely the network is connected, it is possible for
a peer to receive a published message more than once, from different full-message
peers along disjointed paths. When this happens, duplicates of already received
messages are discarded. Libp2p refers to this sharing and rebroadcasting of messages
as gossipsub.
NOTE Full detail for the JavaScript implementation of Libp2p gossipsub is available on Github at:
https://github.com/ChainSafe/js-libp2p-gossipsub
In addition to sharing published messages with all full-message peers, Libp2p randomly
selects a portion of metadata-only peers to share metadata about the messages it has
received. At the time of writing the default number of random metadata-only peers that
are sent full messages is also 6 (the same as for full-message peers). If a message does
not arrive from a full-message peer as intended, it can be discovered through
communications with metadata-only peers.
When missed messages are discovered through shared metadata, the full message is
then requested from the metadata-only peer. Message metadata sharing also happens
during the configured heartbeat interval, which by default is every 1 second.
Configuring your future Web3 applications to take advantage of Libp2p networking will
always be a balance of trade-offs. The following section provides a final high-level
summary of all data exchanged during Libp2p pub/sub operations which can be used as
a quick reference for modeling application data usage.
Figure 6.4 shows this same pub/sub state data as a visual mapping.
Figure 6.4 A summary of data maintained as part of Libp2ps pub/sub state. Source: https://docs.
libp2p.io/c oncepts/pubsub/overview/#state
In the following sections, you see Libp2p pub/sub in action through updating the code of
the Code Radio application to use the pub/sub features of Libp2p.
With required dependencies installed, pub/sub functionality can be used within Code
Radio. Using the existing libp2p_node.js JavaScript file created in the previous chapter,
two import statements must be added. The first is to import the floodsub module that
enables pub/sub functionality within Libp2p, the second is to import two functions,
pubsubListen and pubsubBraodcast, which are used to synchronize music data within
Code Radio. The two completed import statements are shown in listing 6.2. Take a
moment to add these to the top of your libp2p_node.js file. Later in the chapter, pub/sub
messages will be managed within these two functions.
Because you are using the Libp2p instance created in the previous chapter, the only
update required is to enable pub/sub by telling Libp2p which protocol it should use. This
is done in the same format that was used to define transport, encryption, and other
protocols within the Libp2p instance. In this case, the protocol used for pub/sub
messages is floodsub. Add this protocol configuration to your Libp2p instance so that it
matches the example shown in listing 6.3.
At this stage, your Libp2p instance can start and make use of pub/sub functionality, but it
has not been configured to do anything with it. To get your instance of Libp2p to send
and receive messages, you are going to ask it to run the pubsubListen() and
pubsubBroadcast() functions that were imported. You will fully configure these functions
in the following sections. Both of these functions take your Libp2p object (which is named
node ) as an input, as shown in listing 6.4.
pubsubListen(node) #1
console.log('synchronizing with the network...')
pubsubBroadcast(node) #1
console.log('broadcasting music to peers...')}
Add the two functions shown in listing 6.4 to your existing startLibp2p function, so that
Libp2p runs them each time it starts. Once added, your startLibp2p function should
match the updated version shown in listing 6.5.
pubsubListen(node) #1
console.log('synchronizing with the network...')
pubsubBroadcast(node) #1
console.log('broadcasting music to peers...')}
#1 Pub/sub functions
This completes the updates required for Code Radios Libp2p instance. When the Code
Radio application starts Libp2p, it also includes additional pub/sub functionality which it
uses by running the two functions pubsubListen() and pubsubBroadcast(). In the sections
that follow, you will build out the logic within these two functions to practice how Libp2p
pub/sub messaging can be used.
NOTE For this chapter, Code Radio’s music files are stored within the ./music_data/ directory
inside of three files: music_classic.js, music_ambient.js, and music_lofi.js.
To configure pub/sub messaging sent by Code Radio, create a new JavaScript file named
music_sync.js within your current working directory, . This is the filename used with the
import statement used earlier in this chapter. Once completed, this new file holds the
application logic that handled synchronizing Code Radios music files. To do this, a few
additional import statements are required. In addition to the items that are imported and
used by the Libp2p node, the music_sync.js files need additional import statements that
define the location of Code Radios music files as well as import statements to allow
processing string values using the fromString and toString modules. Add these import
statements to the top of the new music_sync.js file, so that they match the example
shown in listing 6.6.
To receive information on certain topics using pub/sub, the topics will need to be defined.
These topics can carry any arbitrary name. Topics can also be added or removed over
time. For Code Radio a topic is defined for each of its radio stations. To configure the
new Code Radio topics, start by creating new JavaScript variables named station_1,
station_2, and station_3 within the music_sync.js file. These new variables hold the Code
Radio station name, as shown in listing 6.7.
With the Code Radio station names defined, they can now be used as topics with various
pub/sub functions. To organize pub/sub functions, create two new JavaScript functions
within the music_sync.js file, named pubsubListen() and pubsubBroadcast(), which take
a single input named node . The node input in this case refers to the Code Radio Libp2p
node.
Recall that these are the two functions that were previously imported into the
libp2p_node.js file. For those import statements to work, the JavaScript export keyword
must also be included before the pubsubListen() and pubsubBroadcast() function names.
The following sections will build out Code Radio’s publish and subscribe functionality
using these two functions.
Listing 6.8 Creating subscribe functions to receive Code Radio song updates
#1 Subscribe logic
You now have Code Radio’s subscriptions defined and the application will receive all
messages that are labeled with these topics. What happens when those messages are
received is still not defined however. To trigger some action when messages are
received, you will use a pubsub event listener in addition to the subscription definitions.
The event listener is intended to allow Code Radio to parse the message that is sent
before saving it into memory. To parse the message reliably, you must also decide on a
format for the Libp2p pub/sub messages that are being sent. For this example, Code
Radio will send song names, along with the associated song data. The message format is
therefore defined as:
NOTE The format chosen for Libp2p messages will be unique to each application and is part of its
information architecture (initially mentioned in chapter 2 of this book). Information architecture
is not unique for Web3 and is not covered in depth in this book. For more information on
information architecture, including different ways to organize information within an application,
see "Information architecture models and examples", available at: https://learn.m icrosoft.
com/en-us/sharepoint/information-architecture-m odels-examples
To parse messages in this format, a new object within the pub/sub event listener, named
trackTitle is required. This new object will act as a message consumer which takes the
message that is received and saves the song title that is placed at the beginning of it.
The song title is indicated using the delimiter ::: in this case. From this saved song title,
Code Radio can then compare the songs it already has using an if statement. If a song is
received that is not already in memory, Code Radio can then take both the song title and
the song data and save it to the local storage of its associated radio station. Recall that
the associated radio station is configured as the message topic. It is also here that music
objects such as classicSongs that were imported at the top of the music_sync.js file are
used for the first time. A completed version of this logic for station_1 is shown in listing
6.9
Listing 6.9 Listening for an event within Code Radios subscribe logic
To perform a similar check for messages received for station_2 and station_3, the IF
conditional logic must be updated to include entries for the additional two stations. Take
a moment to update your pubsub event listener so that it matches the example shown in
listing 6.10.
This completes the logic for Code Radio’s music subscriptions. At this point, Code Radio
is configured with three subscriptions, each corresponding to one of its three radio
stations. Code Radio now also includes pub/sub event listener logic that allows it to
perform actions on the messages received from the three subscriptions/stations. The
effect of this logic, when combined with peer discovery functions added in the previous
chapter, means that an instance of Code Radio that contains no music files can join the
network, discover other Code Radio users, and receive messages that contain music files
from other users, assuming they have been programmed to send such messages.
Programming Code Radio to broadcast the songs it has stored locally is covered in the
following section.
Listing 6.11 Creating publish functions to share Code Radio music files
}, 5000) #2
}
Now that the broadcast interval is set, you need to implement Code Radio to share the
list of songs it has during each broadcast interval. Starting again with station_1 as an
example, the songs for this station are stored within the classicSongs object that was
imported earlier. This object holds a dictionary of songs that use the song title as a key
and string-encoded music data as the value. At each interval, you should set Code Radio
to iterate over the items in the classicSongs object and broadcast them using the
pubsub.publish() function.
The pubsub.publish() function takes two inputs:
The subscription topic being broadcast to
The message
To share the music data along with the song, both the key and value are sent inside the
message field, delineated by the character string ":::". The message body is encoded as
an unsigned integer array before it is sent. This is done to separate the key, value, and
delimiter portions of the string so that it can be processed more easily by the receiver.
Listing 6.12 shows a completed version of this broadcast logic for the station_1 topic.
Listing 6.12 Triggering message broadcasts within Code Radios publish logic
Extending this broadcast logic to the two additional stations can be done as shown in
listing 6.13. Take a moment to update the music_sync.js file so that it includes the
additional logic to broadcast all three stations music files.
#1 ambientSongs broadcast
#2 lofiSongs broadcast
The programming needed to allow Code Radio to broadcast the music files it has stored
locally is now complete. Using Libp2p, the Code Radio application now has the ability to
discover new users, connect to new users, broadcast songs that are stored locally, as
well as receive songs that have been broadcast by others. Within Code Radios'
application architecture, all of this functionality happens within Libp2p and at the
applications' base network layer. In the next chapter, the storage layer of Code Radios'
application architecture will be built. Building on top of the network layer that you have
now completed, Code radio will be configured to persist song data client-side, so that it is
available even after the Code Radio application has been closed and restarted.
6.5 Summary
Libp2p can be used to enable message exchanges at the network layer
of Web3 applications.
Pub/sub or publish and subscribe mechanics can be used to allow Web3
applications to share a single state by sending update and status
messages peer-to-peer.
The JavaScript implementation of Libp2p has a native pub/sub module
that can send messages using the floodsub protocol.
When designing Web3 systems that rely on pub/sub communications,
some design targets to keep in mind are reliability, speed, efficiency,
resilience, scale, and simplicity.
Changing the quantity and size of pub/sub messages can affect
application design targets, such as efficiency and scale.
Pub/sub organizes messages by topic.
An application must subscribe to a topic to ensure all messages for that
topic are received.
Anyone can send pub/sub messages to a topic, even if they are not
subscribed to that topic.
Libp2p exchanges two types of messages with peers: full messages and
metadata-only messages.
Libp2p maintains a pub/sub state which includes a list of its
subscriptions, a list of its peers with their subscriptions and peering
type, a history of messages sent to topics that are not subscribed to,
and finally, a list of messages that have been received.
On both the Mac and Linux platforms, the commands required to perform a base64
conversion of the wav music file is natively supported. On Windows platforms, a third-
party tool such as base64.exe can be downloaded to perform the conversion. On a Mac,
the conversion can be performed from the terminal using the following format:
On a Linux machine, line wrapping can cause unexpected behavior in later file
processing. To perform the conversion on a Linux machine without line wrapping, the
command becomes:
Last, from a Windows platform machine, the command to complete the file conversion
will depend on the third-party tool used. When using base64.exe on the Windows
platform, the command becomes:
Back within the new indexedDB.js file, this base64-encoded music file can be stored as a
Javascript object. With base64 encoding, the wav file is made a generic string
representation, and because this string will be converted a Javascript blob object next, it
must also include a MIME type as part of the variable assignment. MIME stands for
Multipurpose Internet Mail Extensions, but are used more broadly today to indicate the
intended document structure or format for data that is otherwise made generic (as done
with base64 encoding). Take a moment to add a new variable named "tranAndInk" to
your indexedDB.js file, using the following example; note that the string value that is
received from base64 encoding the music file in the previous step is included here:
After assigning this variable, it can be referenced and converted into a binary blob
format for use with IndexedDB. The code snippet shown in listing B.1 is published by the
engineer Boris Smus and is available on Github at: gist.github.com/borismus/1032746.
This code snippet takes the base64-encoded variable as an input and returns an
immutable, fixed-length array buffer to be used as a binary blob. Take a moment to add
this code snippet to your indexedDB.js file.
function convertDataURIToBinary(dataURI) {
var BASE64_MARKER = ';base64,';
var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
var base64 = dataURI.substring(base64Index);
var raw = window.atob(base64);
var rawLength = raw.length;
var array = new Uint8Array(new ArrayBuffer(rawLength));
for(i = 0; i < rawLength; i++) {
array[i] = raw.charCodeAt(i);
}
return array;
}
The next step is to store the array that is returned as a binary blob. This is done in two
steps. As referenced earlier, Javascript blob objects require a MIME type. The first step is
to declare the MIME type when storing the array as a blob. The second step is to
generate a URL or Universal Resource Locator for the new blob object. This URL is what
you will use to address and reference the blob object and is created using the
URL.createObjectURL() static method that is part of the URL API. The two steps can be
programmed using the code shown in listing B.2. Take a moment to add these additional
variable assignments to your indexedDB.js file.
NOTE The additional URL that is created is a requirement inherited from the Media Source
specification that handles media playback. More information about the URL.createObjectURL()
static method can be found as part of Mozilla Developer Network documentation at: developer.
mozilla.org/en-US/docs/Web/API/URL/createObjectURL.
Listing B.2. Storing the music file array as a binary blob with a URL address.
You now have an IndexedDB compatible audio file, with an addressable URL paired with
it. The next step is a bit of housekeeping to declare an initial audio source as shown in
listing B.3. Take a moment to add this code to your indexedDB.js file.
The manual encoding of your Code Radio music files are completed at this stage and are
ready to be stored into IndexedDB.