This repository has been archived.
pontarius / pontarius-xmpp Goto Github PK
View Code? Open in Web Editor NEWAn XMPP client library for Haskell.
License: Other
An XMPP client library for Haskell.
License: Other
Once this has been called, the Session
handle is invalid, correct? What will happen if my code tried to use the handle after this?
Uhh... I can't build this library anymore. My compiler does not support template Haskell (cross-compiling for ARM).
Any chance template Haskell stuff could get at very least separated into a different package? Otherwise... I'm not sure what I'll do.
I tried this:
presenceStream s = forever $ waitForPresence (const True) s >>= print
msgStream s = forever $ getMessage s >>= print
main = do
s <- setupSession
presenceStream =<< dupSession s
msgStream =<< dupSession s
forever (return ())
But it seemed to only print out presence stanzas. I commented out the presenceStream call and then it showed messages. So does dupSession
not do what I think it does?
Even though there seems to be a type for specifying more details (AuthFailure
)
RFC 6120 states:
If one party to an XML stream detects that the other party has attempted to send XML data with an encoding other than UTF-8, it MUST close the stream with a stream error, which SHOULD be (Section 4.9.3.22)...
8 of 29] Compiling Network.Xmpp.Stream ( source/Network/Xmpp/Stream.hs, dist/build/Network/Xmpp/Stream.o )
source/Network/Xmpp/Stream.hs:632:21:
Couldn't match type Either DNSError [IPv6]' with
Maybe [IPv6]'
Expected type: IO (Either GIE.IOException (Maybe [IPv6]))
Actual type: IO (Either GIE.IOException (Either DNSError [IPv6]))
In a stmt of a 'do' block:
aaaaResults <- (Ex.try
$ rethrowErrorCall
$ withResolver resolvSeed
$ \ resolver -> lookupAAAA resolver domain) ::
IO (Either GIE.IOException (Maybe [IPv6]))
In the expression:
do { aaaaResults <- (Ex.try
$ rethrowErrorCall
$ withResolver resolvSeed
$ \ resolver -> lookupAAAA resolver domain) ::
IO (Either GIE.IOException (Maybe [IPv6]));
handle <- case aaaaResults of {
Right Nothing -> return Nothing
Right (Just ipv6s) -> connectTcp $ map (\ ip -> ...) ipv6s
Left _e -> return Nothing };
case handle of {
Nothing -> do { ... }
Just handle' -> return $ Just handle' } }
In an equation for `resolvAndConnectTcp':
resolvAndConnectTcp resolvSeed domain port
= do { aaaaResults <- (Ex.try
$ rethrowErrorCall
$ withResolver resolvSeed $ \ resolver -> ...) ::
IO (Either GIE.IOException (Maybe [IPv6]));
handle <- case aaaaResults of {
Right Nothing -> return Nothing
Right (Just ipv6s) -> connectTcp $ map (\ ip -> ...) ipv6s
Left _e -> return Nothing };
case handle of {
Nothing -> ...
Just handle' -> return $ Just handle' } }
source/Network/Xmpp/Stream.hs:643:26:
Couldn't match type Either DNSError [IPv4]' with
Maybe [IPv4]'
Expected type: IO (Either GIE.IOException (Maybe [IPv4]))
Actual type: IO (Either GIE.IOException (Either DNSError [IPv4]))
In a stmt of a 'do' block:
aResults <- (Ex.try
$ rethrowErrorCall
$ withResolver resolvSeed
$ \ resolver -> lookupA resolver domain) ::
IO (Either GIE.IOException (Maybe [IPv4]))
In the expression:
do { aResults <- (Ex.try
$ rethrowErrorCall
$ withResolver resolvSeed
$ \ resolver -> lookupA resolver domain) ::
IO (Either GIE.IOException (Maybe [IPv4]));
handle' <- case aResults of {
Left _ -> return Nothing
Right Nothing -> return Nothing
Right (Just ipv4s) -> connectTcp $ map (\ ip -> ...) ipv4s };
case handle' of {
Nothing -> return Nothing
Just handle'' -> return $ Just handle'' } }
In a case alternative:
Nothing
-> do { aResults <- (Ex.try
$ rethrowErrorCall
$ withResolver resolvSeed $ \ resolver -> ...) ::
IO (Either GIE.IOException (Maybe [IPv4]));
handle' <- case aResults of {
Left _ -> return Nothing
Right Nothing -> return Nothing
Right (Just ipv4s) -> connectTcp $ map (\ ip -> ...) ipv4s };
case handle' of {
Nothing -> return Nothing
Just handle'' -> return $ Just handle'' } }
source/Network/Xmpp/Stream.hs:688:13:
Couldn't match expected type Either DNSError [(Int, Int, Int, Domain)]' with actual type
Maybe [(t0, t1, t2, String)]'
In the pattern: Just [(_, _, , ".")]
In a case alternative:
Just [(, _, , ".")]
-> do { debugM "Pontarius.Xmpp" $ ""." SRV result returned.";
return $ Just [] }
In a stmt of a 'do' block:
case srvResult of {
Just [(, _, _, ".")]
-> do { debugM "Pontarius.Xmpp" $ ""." SRV result returned.";
return $ Just [] }
Just srvResult'
-> do { debugM "Pontarius.Xmpp"
$ "SRV result: " ++ (show srvResult');
orderedSrvResult <- orderSrvResult srvResult';
.... }
Nothing
-> do { debugM "Pontarius.Xmpp" "No SRV result returned.";
return Nothing } }
source/Network/Xmpp/Stream.hs:691:13:
Couldn't match expected type Either DNSError [(Int, Int, Int, Domain)]' with actual type
Maybe [(Int, Int, Int, Domain)]'
In the pattern: Just srvResult'
In a case alternative:
Just srvResult'
-> do { debugM "Pontarius.Xmpp"
$ "SRV result: " ++ (show srvResult');
orderedSrvResult <- orderSrvResult srvResult';
return
$ Just
$ map (\ (_, , port, domain) -> (domain, port)) orderedSrvResult }
In a stmt of a 'do' block:
case srvResult of {
Just [(, _, _, ".")]
-> do { debugM "Pontarius.Xmpp" $ ""." SRV result returned.";
return $ Just [] }
Just srvResult'
-> do { debugM "Pontarius.Xmpp"
$ "SRV result: " ++ (show srvResult');
orderedSrvResult <- orderSrvResult srvResult';
.... }
Nothing
-> do { debugM "Pontarius.Xmpp" "No SRV result returned.";
return Nothing } }
source/Network/Xmpp/Stream.hs:698:13:
Couldn't match expected type Either DNSError [(Int, Int, Int, Domain)]' with actual type
Maybe t3'
In the pattern: Nothing
In a case alternative:
Nothing
-> do { debugM "Pontarius.Xmpp" "No SRV result returned.";
return Nothing }
In a stmt of a 'do' block:
case srvResult of {
Just [(_, _, _, ".")]
-> do { debugM "Pontarius.Xmpp" $ ""." SRV result returned.";
return $ Just [] }
Just srvResult'
-> do { debugM "Pontarius.Xmpp"
$ "SRV result: " ++ (show srvResult');
orderedSrvResult <- orderSrvResult srvResult';
.... }
Nothing
-> do { debugM "Pontarius.Xmpp" "No SRV result returned.";
return Nothing } }
Failed to install pontarius-xmpp-0.3.0.1
Is a PresenceType
of Default
the same as sending Nothing
for Presence.presenceType
?
Using getMessage
is returning message errors as though they were type=normal messages:
<message id='[email protected]/haskell' type='error' to='[email protected]/haskell' from='[email protected]'><error type='cancel'><remote-server-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/><text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Server-to-server connection failed: closed</text></error></message>
Message {messageID = Just [email protected]/haskell, messageFrom = Just [email protected], messageTo = Just [email protected]/haskell, messageLangTag = Nothing, messageType = normal, messagePayload = [Element {elementName = Name {nameLocalName = "error", nameNamespace = Just "jabber:client", namePrefix = Nothing}, elementAttributes = [(Name {nameLocalName = "type", nameNamespace = Nothing, namePrefix = Nothing},[ContentText "cancel"])], elementNodes = [NodeElement (Element {elementName = Name {nameLocalName = "remote-server-not-found", nameNamespace = Just "urn:ietf:params:xml:ns:xmpp-stanzas", namePrefix = Nothing}, elementAttributes = [], elementNodes = []}),NodeElement (Element {elementName = Name {nameLocalName = "text", nameNamespace = Just "urn:ietf:params:xml:ns:xmpp-stanzas", namePrefix = Nothing}, elementAttributes = [], elementNodes = [NodeContent (ContentText "Server-to-server connection failed: closed")]})]}]}
RFC 6120 states:
When the receiving entity offers the SASL EXTERNAL mechanism, the receiving entity SHOULD list the EXTERNAL mechanism first among its offered SASL mechanisms and the initiating entity SHOULD attempt SASL negotiation using the EXTERNAL mechanism first (this preference will tend to increase the likelihood that the parties can negotiate mutual certificate authentication).
We might want our users to be able to disable this behaviour.
Please update to conduit 1.0 first. I was going to send a pull request, but I find that Philonous/pontarius has done much more.
I am trying to connect to gtalk with echoclient/Main.hs. I substitute example.com
to google.com
and fill in my google id and password, the response is
Performing SRV lookup...
SRV result: [(20,0,5222,"alt4.xmpp.l.google.com."),(20,0,5222,"alt3.xmpp.l.google.com."),(20,0,5222,"alt1.xmpp.l.google.com."),(20,0,5222,"alt2.xmpp.l.google.com."),(5,0,5222,"xmpp.l.google.com.")]
SRV records found, performing A/AAAA lookups...
Connecting to 2404:6800:4008:c01:00:00:00:7d on port 5222.
Connection to HostName could not be established.
Connecting to 74.125.31.125 on port 5222.
Successfully connected to HostName.
Acquired handle.
Setting NoBuffering mode on handle.
Starting stream...
Received TCP data: <stream:stream from="google.com" id="84FD8598880E319B" version="1.0" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client"><stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"><required/></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>X-OAUTH2</mechanism><mechanism>X-GOOGLE-TOKEN</mechanism></mechanisms></stream:features>.
Received TCP data: <stream:error><not-authorized xmlns="urn:ietf:params:xml:ns:xmpp-streams"/></stream:error></stream:stream>.
closeStreamWithError: Stream has no language tag
xmppSasl: Attempts to authenticate...
Attempts to bind...
*** Exception: XmppFailure: StreamEndFailure
RFC 6122 states:
XMPP servers MUST, and XMPP clients SHOULD, support [IDNA2003] for domainparts (including the [NAMEPREP] profile of [STRINGPREP]), the Nodeprep (Appendix A) profile of [STRINGPREP] for localparts, and the Resourceprep (Appendix B) profile of [STRINGPREP] for resourceparts; this enables XMPP addresses to include a wide variety of characters outside the US-ASCII range. Rules for enforcement of the XMPP address format are provided in [XMPP].
See also #62.
In MessageThread
RFC 6120 states:
If the domainpart is the same as the source domain, derived domain, or resolved IPv4 or IPv6 address to which the initiating entity originally connected (differing only by the port number), then the initiating entity SHOULD simply attempt to reconnect at that address.
See also #51.
When trying to build against a conduit 0.5 series (which the .cabal file still advertises support for) it failed, complaining about a missing ConduitM
RFC 6120 states:
In accordance with Section 7.2.1 of [TLS], to help prevent a truncation attack the party that is closing the stream MUST send a TLS close_notify alert and MUST receive a responding close_notify alert from the other party before terminating the underlying TCP connection(s).
With -O0
the following causes a stack space overflow, with -O2
it just causes an infinite loop:
ims s = forever $ do
m <- getMessage s
print m
print (getIM m)
When I send a message, the first print succeeds, it is the second that crashes / never terminates.
Not sure how much XEP-related code you want in pontarius. I've written the following:
module Nick (getNick) where
import Data.XML.Types (Element, Name(..))
import Data.XML.Pickle (UnpickleError, PU, xpUnliftElems, xpElemText, unpickle)
import qualified Data.Text as T
getNick :: [Element] -> Either UnpickleError T.Text
getNick = unpickle xpNick
xpNick :: PU [Element] T.Text
xpNick = xpUnliftElems $ xpElemText nickElem
nickElem :: Name
nickElem = Name (T.pack "nick")
(Just $ T.pack "http://jabber.org/protocol/nick") Nothing
I'm quite happy to let it live in my app for now, but if this is the sort of thing you guys are interested in, I could write up a pull request that adds something similar to pontarius.
RFC 6121 states:
If the server does not advertise support for subscription pre-approvals, the client MUST NOT attempt to pre-approve subscription requests from potential or actual contacts.
The Session
already has an ID generator used for IQs. For tracking errors coming back from messages, or XEP-0198 related functionality it is useful to know the message IDs, but I don't really care what they are, so reusing the generator seems useful.
newStanzaId :: Session -> IO StanzaID
RFC 6120 states:
An implementation MUST NOT generate namespace prefixes for elements qualified by the content namespace (i.e., the default namespace for data sent over the stream) if the content namespace is 'jabber:client' or 'jabber:server'. [...] An XMPP entity SHOULD NOT accept data that violates this rule.
For example, the following is illegal:
<stream:stream
from='[email protected]'
to='im.example.com'
version='1.0'
xml:lang='en'
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'>
<foo:message xmlns:foo='jabber:client'>
<foo:body>foo</foo:body>
</foo:message>
RFC 6120 states:
An implementation SHOULD make it possible for an end user or service administrator to provision a deployment with specific trust anchors for the certificate presented by a connecting entity (either client or server); when an application is thus provisioned, it MUST NOT use a generic PKI trust store to authenticate the connecting entity.
The document continues:
The initial stream and the response stream MUST be secured separately, although security in both directions MAY be established via mechanisms that provide mutual authentication.
We may or may not conform to these requirements.
RFC 6120 states:
A stanza SHOULD possess an 'xml:lang' attribute (as defined in Section 2.12 of [XML]) if the stanza contains XML character data that is intended to be presented to a human user (as explained in [CHARSETS], "internationalization is for humans"). The value of the 'xml:lang' attribute specifies the default language of any such human-readable XML character data.
RFC 6120 states the following in regards to comments, processing instructions, internal or external DTD subsets, as well as internal or external entity references (with the exception of predefined entities):
If an XMPP implementation receives characters matching such features over an XML stream, it MUST close the stream with a stream error, which SHOULD be (Section 4.9.3.18)...
RFC 6120 states, in regards to <see-other-host />:
In addition, the initiating entity SHOULD abort the connection attempt after a certain number of successive redirects (e.g., at least 2 but no more than 5).
See also #51.
RFC 6120 states:
The numbering scheme for XMPP versions is ".". The major and minor numbers MUST be treated as separate integers and each number MAY be incremented higher than a single digit. Thus, "XMPP 2.4" would be a lower version than "XMPP 2.13", which in turn would be lower than "XMPP 12.3". Leading zeros (e.g., "XMPP 6.01") MUST be ignored by recipients and MUST NOT be sent.
I remember writing such a version number parser once upon a time. I'll try to locate it.
See also the following paragraphs, including:
The minor version number will be incremented only if significant new capabilities have been added to the core protocol (e.g., a newly defined value of the 'type' attribute for message, presence, or IQ stanzas). The minor version number MUST be ignored by an entity with a smaller minor version number, but MAY be used for informational purposes by the entity with the larger minor version number (e.g., the entity with the larger minor version number would simply note that its correspondent would not be able to understand that value of the 'type' attribute and therefore would not send it).
RFC 6120 states:
Because XMPP uses long-lived XML streams, it is possible that a certificate presented during stream negotiation might expire or be revoked while the stream is still live (this is especially relevant in the context of server-to-server streams).
Therefore, each party to a long-lived stream SHOULD do the following (from the RFC):
After the stream is closed, the initiating entity from the closed stream will need to reconnect and the receiving entity will need to authenticate the initiating entity based on whatever certificate it
presents during negotiation of the new stream.
RFC 6120 states that client implementations MUST fulfill the following:
Do not generate or accept internal or external entity references with the exception of the predefined entities.
RFC 6120 states the following in regards to XML validation:
Clients are advised not to rely on the ability to send data that does not conform to the schemas, and SHOULD ignore any non-conformant elements or attributes on the incoming XML stream.
Currently getIMPresence
returns Nothing
if the contact is sending a normal "online" or Unavailable
presence, but this means we cannot use this as a unified interface for extracting status text.
RFC 6120 states:
A element that contains at least one mandatory-to-negotiate feature indicates that the stream negotiation is not complete and that the initiating entity MUST negotiate further features.
We need to look for <required /> elements, and throw an exception if one is present.
I'm currently getting:
*** Exception: HandshakeFailed (Error_Protocol ("version TLS10is not supported",True,ProtocolVersion))
It seems to me that session
should catch this exception and produce Left (TlsError ...)
Hi, I found some problems with the pontarius-xmpp library when trying to communicate with GCM (Google cloud messaging services) servers through XMPP (CCS - Cloud Connection Service).
I can't start a session. The problem happens when exchanging the first messages with the server. I found where is the problem. It is inside the "openElementFromEvents" function, when calling the "throwOutJunk" function. The problem is that there is Nothing, which means the stream is closed, so it throws `XmppOtherFailure'.
After exchanging some emails with Jon Kristensen, he pointed out:
"Google's GCM CCS needs a TLS connection, and not a plain-text connection secured with StartTLS. This is not part of the XMPP standard, and Pontarius XMPP does not currently support it."
I was wondering if there is a way to connect using TLS (instead of StartTLS).
Here I paste a simple test example:
{-# LANGUAGE OverloadedStrings #-}
import Data.Default
import Network
import Network.Xmpp
import System.Log.Logger
main :: IO ()
main = do
updateGlobalLogger "Pontarius.Xmpp" $ setLevel DEBUG
result <- session
"gcm.googleapis.com"
(Just ( \_ -> [plain "[email protected]" Nothing "apikey"] , Nothing))
def {
sessionStreamConfiguration = def {
connectionDetails = UseHost "gcm.googleapis.com" (PortNumber 5235)
}
}
return ()
I am getting this output:
Opening stream...
Connecting to configured address.
Connecting to gcm.googleapis.com on port PortNumber 5235.
Successfully connected to HostName.
Acquired handle.
Setting NoBuffering mode on handle.
Starting stream...
Out: <stream:stream version="1.0" to="gcm.googleapis.com" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams">
openElementFromEvents: Stream ended.
I hope you could help me!
Thanks!
So it cannot be used to set preferences in the session settings.
data Something = Something Jid deriving (Read, Show)
(read $ show $ Something $ read "[email protected]") :: Something -- exception
Why not just make the type an instance of Default
instead of providing a special symbol for the empty message?
Is there a clean way to only allow SASL PLAIN when the connection has TLS?
Section 13.8.1 of RFC 6120 states:
For authentication only, servers and clients MUST support the SASL Salted Challenge Response Authentication Mechanism [SCRAM] -- in particular, the SCRAM-SHA-1 and SCRAM-SHA-1-PLUS variants.
I have created an hs-tls issue, haskell-tls/hs-tls#17, to allow us to tap into the data needed for this.
Note that channel binding SHOULD be used even if TLS is used.
Which makes pullStanza
less than useful.
A lot of the functions return Maybe
now, with Nothing
if the send fails. What causes send to fail: connection down?
And record selectors also not exported. No way to construct an InstantMessage
.
RFC 6121 states:
For tracking purposes, a client SHOULD include an 'id' attribute in a presence subscription request.
It is recommended for XMPP implementations to periodically send whitespace down the channel in order to keep the connection alive and also to check that the connection is still alive. Does the library do this yet?
RFC 6120 states:
The 'type' attribute is REQUIRED for IQ stanzas. The value MUST be one of the following; if not, the recipient or an intermediate router MUST return a stanza error (Section 8.3.3.1).
The allowed types are: get, set, result, and error.
We should not terminate the stream.
RFC 6120 states, in regards to namespaces for extended content in stream headers:
If either party to a stream declares such namespaces, the other party to the stream SHOULD close the stream with an stream error (Section 4.9.3.10).
First of all - thanks for the good xmpp library =)
I'm trying to receive and print a message
printMsgs (m:msgs) = do
print $ bodyContent m
printMsg msgs
handleMsgs sess = forever $ do
msg <- getMessage sess
printMsg $ imBody $ fromJust $ getIM msg
but when a message appears, an "Unpickle failed" error occurs
in: <message to="[email protected]/CEBD4BB4" type="chat" id="BC2C5693EA656486_2" iconset="classic" from="[email protected]/gmail.94E17DC6"><body>test message</body><cha:active xmlns:cha="http://jabber.org/protocol/chatstates"/><nos:x value="disabled" xmlns:nos="google:nosave"/><arc:record otr="false" xmlns:arc="http://jabber.org/protocol/archive"/></message>
pullUnpickle: Unpickle failed: Error while unpickling:
-> xpRoot
-> xpUnliftElems
-> xpEither (Right)
-> xpStanza
-> xpAlt
1)
Entity not found: "{jabber:client}iq"
2)
Entity not found: "{jabber:client}iq"
3)
Entity not found: "{jabber:client}iq"
4)
-> xpWrap
-> xpMessageError
-> xpWrap
-> xpElem ("{jabber:client}message")
-> attrs
-> xp5Tuple (1)
-> xpWrapMaybe_
expected fixed attribute "type"="error"
5)
-> xpWrap
-> xpMessage
-> xpWrap
-> xpElem ("{jabber:client}message")
-> attrs
Leftover Entities: [(Name {nameLocalName = "iconset", nameNamespace = Nothing, namePrefix = Nothing},[ContentText "classic"])]
6)
Entity not found: "{jabber:client}presence"
7)
Entity not found: "{jabber:client}presence"
Read error: XmppOtherFailure
Closing stream
Sending closing tag
Waiting for stream to close
Maybe I'm doing something wrong?
RFC 6121 states:
For tracking purposes, a client SHOULD include an 'id' attribute in a subscription approval or subscription denial; this 'id' attribute MUST NOT mirror the 'id' attribute of the subscription request.
This would be a useful utility to include:
toBare (Jid local domain _) = Jid local domain Nothing
In Section 4.9.3.19 of RFC 6120, there is a requirement where the initiating entity MUST resolve the FQDN specified in the <see-other-host /> element according to the process described in Section 3.2.
<stream:error>
<see-other-host xmlns='urn:ietf:params:xml:ns:xmpp-streams'>
[2001:41D0:1:A49b::1]:9222
</see-other-host>
</stream:error>
Also:
When negotiating a stream with the host to which it has been redirected, the initiating entity MUST apply the same policies it would have applied to the original connection attempt (e.g., a policy requiring TLS), MUST specify the same 'to' address on the initial stream header, and MUST verify the identity of the new host using the same reference identifier(s) it would have used for the original connection attempt (in accordance with [TLS-CERTS]).
RFC 6122 states:
The domainpart for every XMPP service MUST be a fully qualified domain name (FQDN; see [DNS]), IPv4 address, IPv6 address, or unqualified hostname (i.e., a text label that is resolvable on a local network).
The same document also mandates domain names to be internationalized:
A domainpart consisting of a fully qualified domain name MUST be an "internationalized domain name" as defined in [IDNA2003]; that is, it MUST be "a domain name in which every label is an internationalized label" and MUST follow the rules for construction of internationalized domain names specified in [IDNA2003].
However, while server implementations MUST support IDNA2013, client implementations only SHOULD.
There are also a length restriction on the domainpart:
A domainpart MUST NOT be zero bytes in length and MUST NOT be more than 1023 bytes in length. This rule is to be enforced after any mapping or normalization resulting from application of the Nameprep profile of stringprep (e.g., in Nameprep some characters can be mapped to nothing, which might result in a string of zero length). Naturally, the length limits of [DNS] apply, and nothing in this document is to be interpreted as overriding those more fundamental limits.
RFC 6121 states:
Before ending its presence session with a server, the user's client SHOULD gracefully become unavailable by sending "unavailable presence", i.e., a presence stanza that possesses no 'to' attribute and that possesses a 'type' attribute whose value is "unavailable". [...] Optionally, the unavailable presence stanza MAY contain one or more elements specifying the reason why the user is no longer available. [...] However, the unavailable presence stanza MUST NOT contain the <priority/> element or the <show/> element, since these elements apply only to available resources.
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.