rotonde / rotonde-client Goto Github PK
View Code? Open in Web Editor NEWRotonde Base Client
Home Page: https://client-neauoire.hashbase.io/
License: MIT License
Rotonde Base Client
Home Page: https://client-neauoire.hashbase.io/
License: MIT License
Following the steps described in this post, I get stuck when trying to reset my rotonde profile. I click on the command text box and press "Ctrl+Shift+Backspace", and nothing happens. After checking the console for errors, I see the following:
rotonde.js:106 Uncaught (in promise) TypeError: Cannot read property 'archive' of null
at Rotonde.reset_with_name (rotonde.js:106)
at Rotonde.reset (rotonde.js:100)
at HTMLTextAreaElement.Operator.key_down (operator.js:269)
Rotonde.reset_with_name @ rotonde.js:106
Rotonde.reset @ rotonde.js:100
Operator.key_down @ operator.js:269
Seems like the `archive' variable is null for some reason. Any ideas?
Using Beaker Version: 0.7.6 Electron: 1.7.4 - Chromium: 58.0.3029.110 - Node: 7.9.0.
After opening console.log, it's filled with:
feed.js:168 Uncaught (in promise) TypeError: url.replace is not a function
at to_hash (feed.js:168)
at Entry.is_visible (entry.js:216)
at Feed.refresh (feed.js:136)
at Home.discover_next (home.js:158)
at Portal.discover (portal.js:60)
at <anonymous>
I want to fix this but I think we should discuss how we'd want to approach the fix? Stretch side nav to fit username lengths? One-column list of usernames? only show first X characters of usernames to prevent stretching? Something else?
Maybe we should institute max username length to make this a simpler fix, since we aren't worried about mobile right now anyway.
edit: for what it's worth for now I just made my list of linked portals into one column, but that would still break things if someone made a sentence long username which on cursory glance of the client code seems possible?
Will there be compatibility issues when Beaker 0.8 arrives with it's Post and Timeline service ?
Thanks
(re-post of the issue i opened in the old repo @ Rotonde/beaker#61. moving it here.)
Drag in or manually link (with >>
) file named Screen Shot.png
.
Upon pressing enter to send, the image linked in the post is broken, trying to load media/content/Screen.jpg
. It seems .jpg extension is defaulted to/assumed since the filename breaks on the space.
(When using drag-and-drop, the image file with its full/with-spaces filename is still copied to the media/content/
directory.)
I noticed that the quote message contained the message being quoted, and the message that it quoted, and that other message that this quoted message quoted. We should either, get rid of the thread, as it might lead to larger portal files, or use this to show the whole thread via an expand button or something. Food for thoughts.
Line 43 in cbd5be0
margin-right:15px;
and later margin-right:60px;
In the README.md under the following:
It says to add images to the "/media/content/" folder but there is no media folder in the User repo. There's only a content folder.
I believe deleting "/media" in the entry.js file on line 38 may fix this issue or adding a media folder with a content sub folder in the User repo?
The user icon appears to map correctly without having a media folder.
Not sure how we want to tackle that specifically except user awareness @neauoire ,
maybe a canonical dat link to fork instead of "any" for now?
Doing the command:
filter @CompositionFore
only adds #null
to the URL, it does not apply the filter.
Whenever a new update is run and the portal.json local is updated with a new version number etc.
Display a notification showing what changed in the new update.
I'm pretty new to the whole p2p thing as well as rotonde but this feels like a UX thing that is a nice to have.
I also wanted to mention the possibility that since the elements are generated in script the possibility of actually having them placed in such a way that users can have a local script that effectively takes the same data as arguments as the generating methods but basically overrides the base methods. Thereby allowing users to have there own style and layout effectivly.
But I'm not sure what the relevance of this is in light of the new breaker 0.8 app structure as well I'm familiar with the C# way of creating virtual and overriding methods in the inheritance structure and not familiar at all as to what the JS equivalent of this is and if it is a thing in JS.
If I click on the mentions tab which autopopulates the commander with filter eli
, this includes results such as posts where somebody has included the word "feeling," since this has the sequence of letters eli
in it. While this isn't a problem if your username is something quite unique that certainly won't be embedded in the other words, I'd imagine this would be even more annoying if your username was just @A, or something short and/or common.
I'm new to Dat and peer-to-peer protocols so please bear with me 🙇
For most portals, I can quite easily tell if a portal owner is offline by the absence of their name in the portal list and their entries in my feed. And it can be confirmed by navigating to their Dat URL and seeing that their portal is unavailable. This tells me that the robustness of our network isn't particularly good and it got me wondering why.
The first issue is one of seeding portals. Am I correct in presuming that a 'follow' does not equate to a 'seed'? If not, could we prompt users to seed a portal once they begin following?
The second issue (introduced with the new split portal/client architecture) relates to the client Dat site. The availability of our portal sites is dependent on our client sites, but we don't encourage users to broadcast the URL of the latter in order for those to be seeded. So, even if the availability of my portal is high, if I'm the only 1 seeding my client site and I go offline, then the portal does too.
Is this accurate? What can we do to remedy the situation and improve the availability of portals?
Side note: when I first heard about this split architecture, I presumed that there would be a single client Dat site that the majority of users would direct their portals to. This would be managed by the OSS stewards, highly seeded, and (hopefully) always available. But now every user has both a portal and a client, was this the intended outcome?
not sure if intentional, but a new user with no posts doesn't show up.
if intended, close this.
portal.json
:
{ "dat": "dat://693cba024a90b2e2ef3c50f3f632f92146065b89254de60c9b81e83c1bc6bc08/"
, "name": "⚫"
, "desc": "Rotonde Test Account"
, "site": "dat://55bb42cb905df9efbdbf537c553e4767bf10b3d60a7fdc18296f40565b28d163/"
, "feed":
[{ "message": "@⚫ 'emoji', more like 'gothji' amiright‽ uphi..ouch"
, "timestamp": 1509000000000
, "target": "dat://693cba024a90b2e2ef3c50f3f632f92146065b89254de60c9b81e83c1bc6bc08/"
}]
, "port": []
}
gives a runtime error:
Uncaught TypeError: Cannot read property '1' of null
at Entry.to_html (dat://2714..a6fe/scripts/entry.js:24)
at Feed.refresh (dat://2714..a6fe/scripts/feed.js:131)
at Feed.<anonymous> (dat://2714..a6fe/scripts/feed.js:119)
at later (dat://2714..a6fe/scripts/feed.js:150)
check in my test account:
rotonde: dat://693cba024a90b2e2ef3c50f3f632f92146065b89254de60c9b81e83c1bc6bc08/
portal.json:
dat://693cba024a90b2e2ef3c50f3f632f92146065b89254de60c9b81e83c1bc6bc08/portal.json
Hello Friends,
I started working on a Rotonde client for iOS. It's read only for now because DAT doesn't run on a phone. Dat is also only single writer so a truly distributed system just isn't possible. This requires a new approach to getting a fully featured mobile Rotonde client working. Push notifications for mentions and Direct mentions would also need a conventional Push Notification server.
I see 2 possible solutions to overcoming these limitations on iOS in particular, and perhaps in facilitating richer services for other mobile clients.
Proxy Publishing: Manage your Rotonde DAT on a server and send commands to the server to then commit. This could be accomplished with a simple API. This method would also be compatible with any generic client. The downside is that now the DAT is on the server and a local Rotonde client would be necessary to truly get it off of your computer.
Feed mirroring: A worker process would monitor changes to an external feed and replicate them on your local feed. The Remote feed would be a feed that you post to from a mobile device through proxy publishing. Mirroring would have to be 2 way, the remote feed "mirror" would then also have to replicate your local changes.
I would prefer to figure out a solution that stays distributed while making it ultra easy to start your own Rotonde. The Repeater server seems necessary.
I plan on using Hashbase to automatically persist any DAT created on the Repeater.
Please let me know what ya'll think, or if you have any ideas about how I should structure this.
It never seems to find the url for a few people. I remember someone made a PR for this today, I think. I'm running out of time and I must sail away, I hope someone can fix this 👍
Good luck!
Jealous of all the new rotonde features I see in other's profiles. How can update rotonde without loosing my posts?
After quoting someone, the content of the quote can be changed by editing portal.json. For example:
{
"message": "wow if true",
"timestamp": 1508357434029,
"target": "dat://2f21e3c122ef0f2555d3a99497710cd875c7b0383f998a2d37c02c042d598485/",
"ref": "62",
"quote": {
"message": "[something scandalous]",
"timestamp": 1508356700224,
"editstamp": 1508356739282
}
}
On github, it is obvious that this repo is part of Rotonde.
But once it is forked, it becomes a repo called "client", which does not give much information.
I should be renamed to something like "rotonde-client".
Ditto for the user repo
Is anyone working on a way to know if you're mentioned by someone who you don't follow?
i presume its due to both instances tying to have write access to the same dat-archive which causes it
currently, when you visit another user's Rotonde page, you see their feed much like they would see it. This makes it difficult to see what they themselves have posted. my proposal is, that by default the feed is filtered by the portal's @name if you are not the owner of the page.
to make it possible to still see their full feed (including posts by others that don't mention them) if one so desires, I made it so that filters get removed immediately upon clicking the "filtering by …" message. this way, you can remove filters even when you don't have an operator.
→PR #63
following @ianh 's advice to discuss the matter in a github issue.
you can test the proposed changes yourself by going to my portal: dat://5276ca2f92b6d508933ac4b01ef9078938f69bed9ca15c462617f531641b7a6b/
Hey all. I wanted to share some things about where we're headed with the next major release of Beaker. We're making some significant updates and we want the Rotonde community to be involved. There are three topics to cover: data models, webdb, and applications.
The way that Rotonde currently works is by writing a single portal.json file which contains all of the activity of the users. This is a fine solution, but it has some nonobvious downsides:
The ideal solution is actually to break the data model up into multiple files, where the posts are put in individual .jsons. This solves both problems because you effectively partition the data.
We realized pretty quickly that breaking up into multiple files made things a little bit harder to develop. To deal with that, we created an indexing crawler called IngestDB.
The idea of Ingest is not only to make working with multiple json files easier, but to greatly simplify application dev overall by treating Dat like a database. So, rather than reading and writing files directly, you can instead interact with an ingest DB, like this:
var postUrl = await db.posts.add(bobsDat, {
text: 'Hello!',
createdAt: Date.now()
})
var recentPosts = await db.posts
.orderBy('createdAt')
.reverse() // most recent first
.limit(30)
.toArray()
From the application's perspective, it feels very much like using a database, but under the hood, Ingest translates records to .json files in the users' dats. So, each record has its own URL, and is stored in its own file. (See "how it works" to learn more about the underlying mechanics.)
Ingest is fairly portable; it works from within a web app in Beaker and from nodejs. We wanted to make sure bots and services could also use it. On top of that, we decided to build an Ingest instance into Beaker core, and we're calling that webdb
.
WebDB is basically an Ingest instance with a core set of data types programmed into it. We're adding it to simplify the job for developers. Here's roughly what the API will look like (it's still being worked on):
await webdb.profiles.get(bob.url)
await webdb.posts.create(alicesDat, {text: 'Hello, world!'})
Why are we creating WebDB? Two reasons.
One, ease of use. Ingest is not difficult to program, but it's more difficult than selecting from a set of semantic APIs. We want a nice set of APIs for devs of all skill levels to sit down and be productive with. Conceptual accessibility and DX is important to us.
Two, webdb creates a compatibility guarantee. By setting up common data model, we can help devs write apps that work together well. If things work correctly, anybody that writes an app using webdb
will be able to read the data from another app that uses it. We'll leave room in the data model for some extensibility as well.
The meta goal is to create an apps ecosystem that's highly customizable, easy to cross-integrate, and easy to build with.
You won't have to use webdb, and it's entirely up to the rotonde community what to do. The idea is to offer 3 levels of abstraction and yall choose what's best for you: webdb at the highest level, your own Ingest for a little more power, or DatArchive
for the most control.
One other thing I want to explain about 0.8, and that's applications.
Our meta goal for applications is for them to be powerful (see this and this), very easy to develop, and very easy to customize.
We've added a formal "application" concept, which is a type of dat which you can install. Installation lets you access privileged APIs. Those include the bookmarks, dat library, history, etc.; they are APIs you might normally expect for a web extension.
Installation also gives you access to the user-defined DNS system, which is called the App Scheme.
To explain -- it's very important to us that users are able to customize their applications. You should be able to to modify any application you use.
One way that apps will create friction against customization is by owning their URL space. The issue is, even if you can fork the code behind dat://someapp.com
, links using that domain will still go to the original version. You need to be able to change the app assigned to dat://someapp.com
to your fork.
So, we created a new URL scheme, app://
, which maps user-chosen names to dat apps. This will get set when users install an Application. The app will suggest a default name to install at, and then users will choose the specific location during the install flow. That solves our problem of customization: you can fork an app, and set your fork to override the app://
URL.
Here is a slightly outdated gif of this in action:
Over time, we also think users will be able to share what they have mapped to their app names, and then p2p system becomes somewhat collaborative about deciding what apps should go at what names.
Rotonde's architecture is close to working with the apps system already.
In the apps architecture, the app code and the user content will be in 2 separate dats, just as recently happened in Rotonde. Users would then choose a Rotonde distro to be installed at app://rotonde
.
The rest of the mechanics are up to yall; one option would be, to view other ppl's rotonde feeds, you visit app://rotonde/{user-dat-key}
. Then if you visit the user's dat directly (dat://{user-dat-key}
) it would load its own version of the rotonde software, as it currently works.
This would make it possible for people to choose their personal app://rotonde
, and also create personalized homepages at their dat://
.
That was a lot! I hope everything was clear. Please feel free to @ me with questions and feedback, and let me know if you have any concerns about the plans.
I think that having a `command history' feature would be quite useful. Very simply modeled: an array of commands executed, the user can shift through them by pressing the "up" and "down" keys.
How does that sound? I can work on it myself.
I think a lot of us have been thinking about how to do bots on Rotonde, so I figured I would share what I've come up with thus far. Most things here feel obvious, but even the obvious benefits from documentation.
You know, the kind of bots you'll see posting every few hours on Twitter.
These are fairly straightforward to implement. You can post automatically whenever you are on your computer with Beaker (I think this is how poemexe got its first start).
You can also make use of dat's command-line interface to share over dat. I posted a guide on how to use this method for bots.
The caveat is that it requires a server of sorts, but I think that the majority of botmakers can accept this for the moment being. (Otherwise I have an experimental project that could be used, but I digress.)
What's really interesting is building interactive bots and services ontop of Rotonde. The way to currently do that, as I see it, is to scrape the network. I'll outline out my thoughts on these matters so far.
I made a bot of this type called greeter, which says hi to you when you greet it!
See the code, follow at dat://1bd0f36ba074263a0dc3f0bcb504c59742b30f8c2d3e15b87cbe9cb04afdf575
Crawler
The crawler grabs all of the known network's dat urls and stuffs them into a list. The list is then shared over as a dat, for everybody to use.
Currently The Rotonde List can be used for this, but the best solution would be to combine the Rotonde List (for finding people that are currently completely unconnected, new converts from Twitter etc) and an automated scraped list (for people that have joined and are connected, but aren't in the list).
(Rotolist by @aeonofdiscord is such a crawler. I based my own crawler off of its code, many thanks to aeon for a good start!)
Scraper
The scraper gets all messages from the network using the crawler generated list and puts it all into a file. The file has one network message per line, for easy parsing. E.g.
for line in scraped:
message = JSON.parse(line)
if message.target == bot.dat:
// do things
The Service
I think the two preceeding components will be pretty much present in any kind of p2p service or interactive bot, but now it becomes more speculative.
The service's portal.json can be shared in the manner outlined in my guide.
My current thoughts for a basic structure are the following:
target=<bot.dat>
bot.dat
in portal.port
Even better is to put this functionality in a client-side module. Using the scraped files this should now be possible.
The first service I wrote was a mention service, as discussed in #46. Its current incarnation is as a bot, but I think this would be a good fit for the client-side idea.
See the code, follow at dat://acf76768f02665825ccf7e205268833e096fc232a8cb933c0c12ee03bdd25ccd/
I maintain a list of the scraped network at dat://cb0fb0216c962b434e87fce98a5293e53595c136eac3f21b0c926a3a0d8a529c
Its content is as follows:
Messages
dat://cb0fb0216c962b434e87fce98a5293e53595c136eac3f21b0c926a3a0d8a529c/scraped.txt
each line has a json object containing a rotonde message as per the Specs, but with the addition of a key called source
, attributing the message to the instance that created it
format <rotonde message object><newline>
Network
dat://cb0fb0216c962b434e87fce98a5293e53595c136eac3f21b0c926a3a0d8a529c/network.txt
each line has a dat portal as scraped from the network using my scraper. note: some of these don't work
format <portal.name><space><dat://..><newline>
Metadata
dat://cb0fb0216c962b434e87fce98a5293e53595c136eac3f21b0c926a3a0d8a529c/metadata.txt
each line contains a dat's portal.json, albeit with the feed set to an empty array
format <dat://...><space><stringified portal.json><newline>
The scraper itself can be found here and bugfixes, patches and similar PRs are very welcome! I would also love to see other people with bigger and better machines start scraping Rotonde!
There are many concerns and ideas around this area, such as how to implement services in the future using Beaker's intents, declining being scraped by using a kind of robots.txt, and many more.
dat-node occasionally breaks when closing utp connections using archive.close()
.
dat-node remember to call both archive.close()
and archive.leave()
, otherwise you will leak memory. (At least I did, I have an open issue where I'll look into it more.
I propose the following format to standardize all currently available commands:
/command param1 param2 ... paramN
So it will be less confusing for end users, and easier for future development. We can then also implement the autocomplete for commands like we have for @ mentions.
I'm having problems getting the https version (via hashbase) of rotonde working.
When I go here: https://rotonde-earthperson.hashbase.io, I get a blank page. So I think the site is live, but maybe rotonde is not configured for hashbase?
I thought that I had to direct to the index.html in the link, but that gave me the same result.
Is anyone else experiencing this issue?
You can reproduce this by following dat://5276ca2f92b6d508933ac4b01ef9078938f69bed9ca15c462617f531641b7a6b/ and his message: this post is from the distant future. it is the year 318857
will always be on top.
If a user has some followers following them via a their raw dat:// ID, and others following them via an alias like a hashbase URL, some mutuals will be incorrectly displayed as non-followers (with ~ instead of @).
Seeing a bug on @poem_exe (linked below) which seems to be caused by it not being subscribed to any portals. After upgrading to v0.1.42, the feed fails to load and just says "Fetching 0 feeds.."
dat://3cc6b590df09ba8d87fef7cb235f0e3d3a6b6f256c7f0b0dc63059a22f638a48/
Most likely because of the return
at this point in scripts/feed.js, though I'm not sure what the most appropriate fix is:
if(entries.length === 0){
this.el.innerHTML = "Fetching "+this.feed_urls.length+" feeds..";
return;
}
I've got some ideas kicking around for this, initially as a simple scripts/custom.js
with a hook function(rotonde) {}
but that could probably be specialized into specific mods.
As an example of a specialized mod: the idea of an embed (youtube video, pico8.com game, instagram post - whatever) being a drop in module that someone provides for portals to opt into. It is important that the fallback remain legible valid for this type of entry, but in my feed, I opt to view custom types of content in my own way. something like scripts/embed/youtube.js
. These might also want a css and maybe even images/assets, so maybe embeds/<type>/*
.
I'll try to formalize how I see this structured soon but opening discussion @cblgh @neauoire
If I set my site (edit:site
) to twitter.com/frozenpandaman
, clicking on the URL in my sidebar will simply append the text to my dat:// URL, i.e. dat://1327c...69838/twitter.com/frozenpandaman
, giving a 404.
It seems if a protocol, e.g. https://, is given, it hides it in the HTML but keeps it in the rendered a href
tag, i.e. just shows twitter.com/frozenpandaman
but the link still works correctly. (I like this!)
The site
field should support dat:// URLs too, of course, but when setting/editing it, it should probably default to http:// if no protocol is given.
One thing that I cannot manage to do but would love to have, is: once the portals are all loaded(before discovery mode), that the port array in the portal.json
gets re-ordered by latest activity so that next time around, the most recent portals gets loaded first.
Anyone wanna give this a shot?
Currently the runes are added by java script to the feed and portal, if it was changed to a css classes system, it would be easier to customize.
For example:
<t class="portal">
<a href="dat://"><i class="rune-self"></i>afk_mcz</a>
</t>
<t class="portal">
<a href="dat://"><i class="rune-both"></i>user_who_follows_you</a>
</t>
<t class="portal">
<a href="dat://"><i class="rune-follow"></i>user_who_doesn't</a>
</t>
:root {
--rune-self: "▲";
--rune-both: "@";
--rune-follow: "~";
}
.rune-self {
content: var(--rune-self);
}
.rune-both {
content: var(--rune-both);
}
.rune-follow {
content: var(--rune-follow);
}
:custom-css {
--rune-self: "▲";
--rune-both: "◢";
--rune-follow: "◣";
}
Seems like the autocomplete mention feature is not working. The problem is this line; there's no such function (autocomplete_name
) in the "index" Rotonde object, which I think does not exist.
Is "index" now named "home"?
is there a plan for direct messages to users? or is that something out of scope?
The current index-based ids will break if items are removed or reordered.
https://www.npmjs.com/package/uuid (or the one-liner https://gist.github.com/jed/982883) could be a good way to generate unique ids for items in the feed. Then it will be easier to find quoted items in own or other's feed.
I wasn't sure whether to PR this because I'm new to the code and I'm unsure if this is intentional.
rotonde-client/scripts/entry.js
Line 39 in cbd5be0
Moving discussion from #68 here:
Once a timeline "fills up" with more recent stuff (specifically, 42 entries + the $rotonde "bot" post) there's no way to see older stuff. Should we allow for multiple pages or even pagination/infinite scrolling so you can see all the way back?
For whispers/mentions, you can use filter
to see missed/older posts, but I don't think there's a way to see older (or not, depending on # of users you're following) posts from other users. Thus, your feed/homepage is essentially limited to the 42 most recent posts (including your own).
In the past it was possible was to revisit posts you made in the past by filtering on your own username filter @my_username
and then you could see your posts.
Now, it shows:
Fetching # feeds...
and doesn't complete the fetch.
An edge case that seems related. If your Rotonde is not following anyone you are unable see your posts. It displays fetching and doesn't complete the fetch.
Filtering on others usernames works as expected.
Correctly mentioning someone is hard. Multiple users can have the same name which can have odd side-effects. Formatting would fix this, we would know exactly which person was mentioned.
Proposed formatting:
<@DAT_URL>
ex. <@dat://7b3b4c71a3156664bde25fb3c671f8ff6fed7554a352b03fb7fb2e403c7567f9/>
This would be replaced with @lux
at feed render.
if i click on the timestamp of a whisper to me, i'm able to quote the post, essentially making the original post text public. perhaps clicking on the timestamp should autofill with whisper:username
instead of quote:username-#
?
thanks to @Ariganu for the idea!
This is the case for both the client and the portal:
rotonde-client
instance: dat://fab5c8f60ac249ccb22e2abf650a8f7f15907aff05c4a5f3140be6abdf5c60bf/.git/
rotonde-portal
instance: dat://809b8c277bbdaa05d47ade6aab2f10345b9999208f0ff73ac8eff24c0988011e/.git/
Before submitting a fix (adding the .git
to the .datignore
files in both repositories) I just wanted to make sure that this wasn't being done for a reason? Because Dat depends on it, for example.
I am about to start writing some tests so we can make sure new pull requests aren't breaking existing feature.
The policy is no external libraries, but this is kind of a must for testing. Is that ok?
I would like to go with Jest as it does not require any configuration and has a very nice UX.
It does require node to run, but that's a pretty common thing for javascript developers so hopefully a minor hurdle. It is also making progress towards being able to run the tests in the browser so that'll make it even easier.
Developers will still need to install jest though through npm with npm install -g jest
.
It'll integrate with Travis CI which can run on each PR so devs don't necessarily have to run it locally before creating a PR. Particularly useful for first time contributors.
Thoughts?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.