Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First version of private-mode, invite-only WeChat #11

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/social.wechat.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"description": "Social provider using WeChat",
"app": {
"script": [
"wechat-social-provider.js"
]
"wechat-social-provider.static.js"
]
},
"dependencies": {
"pgp-e2e": {
Expand Down
212 changes: 99 additions & 113 deletions src/wechat-social-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

var wechat = require("../node_modules/wechat-webclient/wechat.js");

var MESSAGE_TYPE = {
INVITE: 0,
RETURN_INVITE: 1
};

/*
* Constructor for a WechatSocialProvider object.
*/
Expand All @@ -22,12 +27,13 @@ var WechatSocialProvider = function(dispatchEvent) {
this.wxids = 0;

this.CONTACT_NAME_SCHEME = "uProxy_"; // + user1 / user2

this.inviteds = {}; // wxid => invite mapping

this.invitesSent = {}; // wxid => invite timestamp mapping
this.invitesReceived = {}; // wxid => received invite timestamp mapping

this.initState_();
this.initHandlers_();

this.wxidToUsernameMap = {};
}; // End of constructor

/*
Expand All @@ -45,23 +51,24 @@ WechatSocialProvider.prototype.initState_ = function() {

/*
* Initializes event handlers
*/
*/
WechatSocialProvider.prototype.initHandlers_ = function() {

/*
* Defines how to handle a newly received message.
* @param {Object} message
*/
this.client.events.onMessage = function(message) {
var availability = "ONLINE_WITH_OTHER_APP";
if (message.MsgType === this.client.HIDDENMSGTYPE) {
availability = "ONLINE";
}
// Uncomment below when hidden messages can be sent/received reliably.
// if (message.MsgType !== this.client.HIDDENMSGTYPE) {
// return; // No need to consider non-uProxy messages.
// }
var availability = "ONLINE";
var fromUser = this.client.contacts[message.FromUserName];
var fromUserId = this.userProfiles[fromUser.Uin || fromUser.wxid];
var eventMessage = {
"from": {
"userId": fromUserId,
"userId": fromUser.wxid,
"clientId": message.FromUserName,
"status": availability,
"lastUpdated": Date.now(),
Expand All @@ -71,48 +78,24 @@ WechatSocialProvider.prototype.initHandlers_ = function() {
};
try {
var jason = JSON.parse(message.Content);
if (jason.userStatus === 0) {
this.storage.get("invited_" + this.client.thisUser.Uin)
.then(function(invites) {
var iContact = invites[fromUserId];
if (iContact.timestamp < jason.timestamp) {
resolve(true); // If timestamp of my invite sent before theirs, I create chatroom
} else {
resolve(false);
}
}.bind(this), this.client.handleError.bind(this.client))
.then(function(chatroomCreater) {
if (chatroomCreater) {
var chatroomname = this.CONTACT_NAME_SCHEME + this.client.contacts[contact].NickName; // FIXME, better naming scheme
this.client.log(1, "Social Provider: creating chatroom with name " + chatroomname);
var THE_INVISIBLE_CONTACT = "filehelper";
var list = [contact, THE_INVISIBLE_CONTACT];
this.client.webwxcreatechatroom(list)
.then(this.client.webwxbatchgetcontact.bind(this.client),
this.client.handleError.bind(this.client))
.then(this.client.webwxupdatechatroom.bind(this.client, "modtopic", chatroomname),
this.client.handleError.bind(this.client))
//.then(this.client.webwxupdatechatroom.bind(this.client, "delmember", arbitrarycontact), this.client.handleError.bind(this.client))
.then(function(chatroom) {
var hey = "Hey, " + this.client.contacts[contact].NickName + "! We're now friends ";
hey += "on uProxy! I made this group between us so we can use uProxy privately.";
var welcome = {
"type": 1,
"content": hey,
"recipient": chatroom
};
delete this.inviteds[fromUserId];
this.storage.set("invited_" + this.client.thisUser.Uin, this.inviteds);
return this.client.webwxsendmsg(welcome);
}.bind(this), this.client.handleError.bind(this.client));
} else {
delete this.inviteds[fromUserId];
this.storage.set("invited_" + this.client.thisUser.Uin, this.inviteds);
if (jason.userStatus === MESSAGE_TYPE.INVITE ||
jason.userStatus === MESSAGE_TYPE.RETURN_INVITE) {
var wxidOfInvite = this.client.contacts[message.FromUserName].wxid;
this.invitesReceived[wxidOfInvite] = jason.timestamp;
this.storage.set("received_" + this.client.thisUser.Uin, JSON.stringify(this.invitesReceived));

if (this.invitesSent[wxidOfInvite]) {
this.addOrUpdateClient_(this.client.contacts[message.FromUserName], 'ONLINE');
if (jason.userStatus === MESSAGE_TYPE.INVITE) {
var returnInvite = this.createInvisibleInvite(
MESSAGE_TYPE.RETURN_INVITE, wxidOfInvite);
this.client.webwxsendmsg(returnInvite);
}
}.bind(this), this.client.handleError.bind(this.client));
}
return;
}
} catch(e) {
console.error(e); // don't want to kill uProxy, just means we haven't gotten an invite message
return; // don't want to kill uProxy, just means we haven't gotten an invite message
}
this.client.log(5, eventMessage.message, -1);
this.dispatchEvent_("onMessage", eventMessage);
Expand Down Expand Up @@ -163,7 +146,7 @@ WechatSocialProvider.prototype.initHandlers_ = function() {

/*
* Defines how to handle the receiving of a new Icon from the WeChat webservice.
* @ param {Object} JSON object containing the dataURL of the QR code, and
* @ param {Object} JSON object containing the dataURL of the QR code, and
* the HeadImgUrl of the given icon as (iconURLPath).
*/
this.client.events.onIcon = function(iconJSON) {
Expand All @@ -176,7 +159,7 @@ WechatSocialProvider.prototype.initHandlers_ = function() {
if (friend) {
friend.imageData = jason.dataURL;
this.dispatchEvent_('onUserProfile', friend);
} else
} else
this.client.handleError("Icon corresponds to unknown contact.").bind(this);
} catch (e) {
this.client.handleError(e).bind(this);
Expand All @@ -186,14 +169,14 @@ WechatSocialProvider.prototype.initHandlers_ = function() {

/*
* Defines the function that handles the case where the retrieved UUID corresponds
* to the wrong domain for this user trying to get in. Also saves which domain
* to the wrong domain for this user trying to get in. Also saves which domain
* the user was associated with for future reference.
*
* @param {String} referral URL address.
* TODO: referral isn't used, consider excluding.
* @returns {Promise} that fulfills if restepping through the beginning of the
* @returns {Promise} that fulfills if restepping through the beginning of the
* login process went sucessfully, rejects promise if there was an error.
*/
*/
this.client.events.onWrongDom = function(referral) {
this.storage.set("WechatSocialProvider-was-QQ-user", this.client.isQQuser);
return this.preLogin(referral);
Expand All @@ -208,41 +191,33 @@ WechatSocialProvider.prototype.initHandlers_ = function() {
var selfContact = this.client.thisUser.UserName;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove: + Object.keys(this.client.chatrooms).length

if (this.wxids !== expected) {
for (var userName in wxids) {
console.log(userName);
if (userName.startsWith("@@") && this.client.chatrooms[userName]) {
this.client.log(1, "chatroomUser wxid found: " + this.client.chatrooms[userName].NickName);
this.client.chatrooms[userName].wxid = wxids[userName];
if (!this.userProfiles[wxids[userName]]) {
this.wxids++;
}
if (this.client.chatrooms[userName].NickName.startsWith(this.CONTACT_NAME_SCHEME)) {
this.addOrUpdateClient_(this.client.chatrooms[userName], "ONLINE"); //FIXME
}
} else if (!userName.startsWith("@@") && this.client.contacts[userName]){
var wxid = wxids[userName];
this.wxidToUsernameMap[wxid] = userName;
if (!userName.startsWith("@@") && this.client.contacts[userName]){
this.client.log(1, "contact wxid found: " + this.client.contacts[userName].NickName);
this.client.contacts[userName].wxid = wxids[userName];
if (!this.userProfiles[wxids[userName]]) {
this.client.contacts[userName].wxid = wxid;
if (!this.userProfiles[wxid]) {
this.wxids++;
}
if (userName !== this.client.thisUser.UserName) {
//this.addOrUpdateClient_(this.client.contacts[userName], "ONLINE"); //FIXME
this.addUserProfile_(this.client.contacts[userName]);
}
// else {
// this.addOrUpdateClient_(this.client.thisUser, "ONLINE");
// this.addUserProfile_(this.client.thisUser);
// }
// if (this.invitesSent[wxid] && this.invitesReceived[wxid]) {
// this.addOrUpdateClient_(this.client.contacts[userName], "ONLINE");
// }
}
}
if (this.wxids === expected) {
this.client.log(0, "wxids fully resovled");
this.client.webwxgeticon();
this.storage.get("invited_" + this.client.thisUser.Uin)
.then(function(invites) {
for (var invite in invites) {
for (var invitedWxid in this.invitesSent) {
if (!this.invitesReceived[invitedWxid]) {
var invite = this.createInvisibleInvite(MESSAGE_TYPE.INVITE, invitedWxid);
this.client.webwxsendmsg(invite);
} else {
this.addOrUpdateClient_(this.client.contacts[userName], "ONLINE");
}
}.bind(this), this.client.handleError.bind(this.client));
}
this.loggedIn(this.clientStates[selfContact]);
} else {
this.client.log(-1, "wxids not fully resolved");
Expand All @@ -251,7 +226,6 @@ WechatSocialProvider.prototype.initHandlers_ = function() {
}.bind(this);
};


/*
* Initialize this.logger using the module name
*/
Expand All @@ -274,6 +248,16 @@ WechatSocialProvider.prototype.login = function(loginOpts) {
.then(this.client.webwxinit.bind(this.client), this.client.handleError.bind(this))
.then(function () {
setTimeout(this.client.synccheck.bind(this.client), this.syncInterval);
this.storage.get("invited_" + this.client.thisUser.Uin)
.then(function(invitesString) {
var invites = JSON.parse(invitesString);
this.invitesSent = invites || {};
this.storage.get("received_" + this.client.thisUser.Uin)
.then(function(receivedString) {
var received = JSON.parse(receivedString);
this.invitesReceived = received || {};
}.bind(this), this.client.handleError.bind(this.client));
}.bind(this), this.client.handleError.bind(this.client));
this.client.webwxgetcontact(false).then(function() {
this.addOrUpdateClient_(this.client.thisUser, "ONLINE");
this.addUserProfile_(this.client.thisUser);
Expand Down Expand Up @@ -311,7 +295,7 @@ WechatSocialProvider.prototype.sendMessage = function(friend, message) {
"recipient": friend
};
//this.client.log(3, "WechatSocialProvider sending message", msg.content);
this.client.webwxsendmsg(msg).then(fulfullSendMessage, rejectSendMessage);
this.client.webwxsendmsg(msg).then(fulfullSendMessage, rejectSendMessage);
}.bind(this));
};

Expand Down Expand Up @@ -341,7 +325,7 @@ WechatSocialProvider.prototype.logout = function() {
*/
WechatSocialProvider.prototype.addUserProfile_ = function(friend) {
var uid = friend.Uin || friend.wxid || '';
var userProfile = {
var userProfile = {
"userId": uid, // Unique identification number
"name": friend.NickName || '', // Their display name
"lastUpdated": Date.now(),
Expand All @@ -367,27 +351,13 @@ WechatSocialProvider.prototype.addOrUpdateClient_ = function(friend, availabilit
state.lastUpdated = Date.now();
state.lastSeen = Date.now();
} else {
if (friend.MemberCount === 2) {
for (var member in friend.MemberList) {
if (member.UserName !== this.client.thisUser.UserName) {
state = {
"userId": member.Uin || member.wxid || '', // Unique identification number
"clientId": member.UserName, // Session username
"status": availability, // All caps string saying online, offline, or online on another app.
"lastUpdated": Date.now(),
"lastSeen": Date.now()
};
}
}
} else if (friend.MemberCount === 0) {
state = {
"userId": friend.Uin || friend.wxid || '', // Unique identification number
"clientId": friend.UserName, // Session username
"status": availability, // All caps string saying online, offline, or online on another app.
"lastUpdated": Date.now(),
"lastSeen": Date.now()
};
}
state = {
"userId": friend.Uin || friend.wxid || '', // Unique identification number
"clientId": friend.UserName, // Session username
"status": availability, // All caps string saying online, offline, or online on another app.
"lastUpdated": Date.now(),
"lastSeen": Date.now()
};
}
this.clientStates[friend.UserName] = state;
this.dispatchEvent_('onClientState', this.clientStates[friend.UserName]);
Expand All @@ -404,27 +374,43 @@ WechatSocialProvider.prototype.acceptUserInvitation = function(invite) {
// This is just a stub for how some of the invite process will go.
WechatSocialProvider.prototype.inviteUser = function(contact) {
return new Promise(function (resolve, reject) {
var uProxy_info = JSON.stringify({
"userStatus": 0, // friend request
"timestamp": Date.now()
});
var uProxy_invite = {
"type": this.client.HIDDENMSGTYPE,
"content": uProxy_info,
"recipient": contact.UserName
};
this.inviteds[contact.wxid] = uProxy_invite;
var invisible_invite = this.createInvisibleInvite(MESSAGE_TYPE.INVITE, contact);
if (this.invitesReceived[contact]) {
this.addOrUpdateClient_({UserName: this.wxidToUsernameMap[contact], wxid: contact}, "ONLINE");
this.client.webwxsendmsg(invisible_invite);
return;
}
var friendName = "friend";
if (this.client.contacts[this.wxidToUsernameMap[contact]] &&
this.client.contacts[this.wxidToUsernameMap[contact]].NickName) {
friendName = this.client.contacts[this.wxidToUsernameMap[contact]].NickName;
}
var plaintext_invite = {
"type": 1,
"content": "Hey " + this.client.contacts[contact].NickName + "! You should use uProxy!", // FIXME
"recipient": contact.UserName
"content": "Hi " + friendName + "! I'd like to use uProxy with you. You can find more information at www.uproxy.org, or ask me about it!",
"recipient": this.wxidToUsernameMap[contact]
};
this.client.webwxsendmsg(uProxy_invite);
this.client.webwxsendmsg(invisible_invite);
this.client.webwxsendmsg(plaintext_invite);
this.storage.set("invited_" + this.client.thisUser.Uin, this.inviteds);
}.bind(this));
};

WechatSocialProvider.prototype.createInvisibleInvite = function(messageType, recipientWxid) {
var timestamp = this.invitesSent[recipientWxid] || Date.now();
var uProxy_info = JSON.stringify({
"userStatus": messageType,
"timestamp": timestamp
});
var invite = {
"type": this.client.HIDDENMSGTYPE,
"content": uProxy_info,
"recipient": this.wxidToUsernameMap[recipientWxid]
};
this.invitesSent[recipientWxid] = timestamp;
this.storage.set("invited_" + this.client.thisUser.Uin, JSON.stringify(this.invitesSent));
return invite;
};

// Register provider when in a module context.
if (typeof freedom !== 'undefined') {
if (!freedom.social) {
Expand Down