webXMPP

Check-in [bbcc0a9e94]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Tidy up how we choose a host name for the notification click URL. Biggie, wrestle with how sometimes we don't post a new get (or, maybe, it doesn't work) when we get freshly exposed from the Android notification click. Actually go to the trouble of closing the server side of pending GET's when the client goes background (it also exit's the thread serving the GET). Seems to help with the issue, but we'll see...
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | master | trunk
Files: files | file ages | folders
SHA3-256:bbcc0a9e947671cb37a88ad6e768fd630e80e958abe27968c6a32500dc87faa1
User & Date: ajv-899-334-8894@vsta.org 2017-04-21 03:44:16
Context
2017-04-21
20:15
One boo-boo in the ivar name. Workaround for an (apparent) failure to post a new network operation as a page enters focus. check-in: c78e23dae5 user: ajv-899-334-8894@vsta.org tags: master, trunk
03:44
Tidy up how we choose a host name for the notification click URL. Biggie, wrestle with how sometimes we don't post a new get (or, maybe, it doesn't work) when we get freshly exposed from the Android notification click. Actually go to the trouble of closing the server side of pending GET's when the client goes background (it also exit's the thread serving the GET). Seems to help with the issue, but we'll see... check-in: bbcc0a9e94 user: ajv-899-334-8894@vsta.org tags: master, trunk
2017-04-19
18:19
Split out so we can support Chrome via Google's proprietary messaging. check-in: 6645a08598 user: ajv-899-334-8894@vsta.org tags: master, trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to fcm.py.

12
13
14
15
16
17
18
19
20
21
22
23
24
25

26
27
28
29
30
31
32
# Google endpoint
FCMHOST = "fcm.googleapis.com"
FCMPATH = "/fcm/send"

class FCM_mixin(object):
    def __init__(self):
	self.fcmkey = self.config.get("fcmkey")
    def fcm(self, iid_token, title, body):

	data = json.dumps(
	    {"notification": {
		 "title": title,
		 "body": body,
		 "click_action": "/html/xmpp_fcm.html"

		},
	     "to": iid_token,
	    }
	)
	h = httplib.HTTPSConnection(FCMHOST)
	headers = {
	    "Authorization": ("key=%s" % (self.fcmkey,)),







|





|
>







12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Google endpoint
FCMHOST = "fcm.googleapis.com"
FCMPATH = "/fcm/send"

class FCM_mixin(object):
    def __init__(self):
	self.fcmkey = self.config.get("fcmkey")
    def fcm(self, host, iid_token, title, body):

	data = json.dumps(
	    {"notification": {
		 "title": title,
		 "body": body,
		 "click_action":
		     ("https://%s/html/xmpp_fcm.html" % (host,))
		},
	     "to": iid_token,
	    }
	)
	h = httplib.HTTPSConnection(FCMHOST)
	headers = {
	    "Authorization": ("key=%s" % (self.fcmkey,)),

Changes to get.py.

11
12
13
14
15
16
17

18
19
20
21
22
23
24
...
173
174
175
176
177
178
179
180
181
182
183




184
185
186
187
188
189
190
191
192
193
194
195
196

197
#	messages.  Each message has "from", "to", and "body"
#	keys, with appropriate text.
#  /config.json
#	Return JSON of configuration with parameters the
#	browser's JS should use in communicating with this
#	server.
import sys, os, threading


# A little static function to order accounts based on status
# Each argument is a (acct-name, acct-status) tuple
def _order_stat(a, b):
    aname,astat = a
    bname,bstat = b

................................................................................
	    acct = tup[0]
	    acct.sendPresence("chat" if vis else "away")

	# Need to wait?
	if timeout and (gen >= user.gen) and (rgen >= user.rgen):
	    # Queue us & sleep for release
	    sema = threading.Semaphore(0)
	    user.await(timeout, sema)
	    sema.acquire()
	    # Either a timeout kicked us loose,
	    #  or a message arrived and did so





	# Supply what we have.  (It may well be empty.)
	return True,self.send_current(gen, rgen)

    # Supply server config params
    def send_config(self):
	if not self.path_match("config.json"):
	    return False,None

	cfg = self.server.approot.config
	d = {}
	for k in ("poll1", "pollslop", "nmsg"):
	    d[k] = cfg[k]

	return True,self.send_json(d)







>







 







|



>
>
>
>













>

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#	messages.  Each message has "from", "to", and "body"
#	keys, with appropriate text.
#  /config.json
#	Return JSON of configuration with parameters the
#	browser's JS should use in communicating with this
#	server.
import sys, os, threading
import chore

# A little static function to order accounts based on status
# Each argument is a (acct-name, acct-status) tuple
def _order_stat(a, b):
    aname,astat = a
    bname,bstat = b

................................................................................
	    acct = tup[0]
	    acct.sendPresence("chat" if vis else "away")

	# Need to wait?
	if timeout and (gen >= user.gen) and (rgen >= user.rgen):
	    # Queue us & sleep for release
	    sema = threading.Semaphore(0)
	    user.await(timeout, sema, vals.get("clientID", "XXX"), self)
	    sema.acquire()
	    # Either a timeout kicked us loose,
	    #  or a message arrived and did so

	    #  or... we were aborted
	    if self.socket is None:
		sys.exit(0)

	# Supply what we have.  (It may well be empty.)
	return True,self.send_current(gen, rgen)

    # Supply server config params
    def send_config(self):
	if not self.path_match("config.json"):
	    return False,None

	cfg = self.server.approot.config
	d = {}
	for k in ("poll1", "pollslop", "nmsg"):
	    d[k] = cfg[k]
	d["clientID"] = chore.utils.tstamp()
	return True,self.send_json(d)

Changes to js/fcm.js.

42
43
44
45
46
47
48

49
50
51
52
53
54
55
// UI exposure
let bg = false;

// For Chrome, when we lose focus we'll have to use their push API
function visChange(ev) {
    const vs =  document.visibilityState;


    if (vs == "hidden") {
	if (!bg) {
	    bg = true;
	    // Abort long polling since Chrome will stop running soon
	    goOffline();

	    // Wait for UI exposure







>







42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// UI exposure
let bg = false;

// For Chrome, when we lose focus we'll have to use their push API
function visChange(ev) {
    const vs =  document.visibilityState;

    console.log("visChange", vs, bg);
    if (vs == "hidden") {
	if (!bg) {
	    bg = true;
	    // Abort long polling since Chrome will stop running soon
	    goOffline();

	    // Wait for UI exposure

Changes to js/ui.js.

72
73
74
75
76
77
78



79
80
81
82
83
84
85
...
415
416
417
418
419
420
421



422
423
424
425
426
427
428
let rxTO = null;

// Dynamic page title; tells us when we heard from the server
let lastTM = '';

// Latest serial # back from server completion
let lastSerial = 0;




// Returning online (network change, cell->wifi, etc.)
function goOnline() {
    if (curReq != null) {
	curReq.abort();
	curReq = null;
    }
................................................................................

    // Our XHR timeout, slop on top of requested server timeout,
    //  scaled for use by setTimeout() as milliseconds.
    rxTimeout = Math.round(1000 * ourConfig.pollslop * ourConfig.poll1);

    // Receive timeout, as string
    reqTimeout = ourConfig.poll1.toString();




    // Now get initial messages to display
    moreMessages();
}

// Entry point for processing
function ourStart() {







>
>
>







 







>
>
>







72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
...
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
let rxTO = null;

// Dynamic page title; tells us when we heard from the server
let lastTM = '';

// Latest serial # back from server completion
let lastSerial = 0;

// Unique value for us
let clientID = null;

// Returning online (network change, cell->wifi, etc.)
function goOnline() {
    if (curReq != null) {
	curReq.abort();
	curReq = null;
    }
................................................................................

    // Our XHR timeout, slop on top of requested server timeout,
    //  scaled for use by setTimeout() as milliseconds.
    rxTimeout = Math.round(1000 * ourConfig.pollslop * ourConfig.poll1);

    // Receive timeout, as string
    reqTimeout = ourConfig.poll1.toString();

    // Get a unique value for this client
    clientID = ourConfig.clientID;

    // Now get initial messages to display
    moreMessages();
}

// Entry point for processing
function ourStart() {

Changes to put.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20



21
22
23
24
25
26




























27

28
29
30
31
32
33
34
#
# put.py
#	HTML PUT handling
# /user? - Update user status
#	[fg|bg] - Tab open?
#
import sys, json
import chore

class PUT_mixin(object):
    def __init__(self):
	self.dispatchers.append( ("PUT", self.put_user) )

    # Set various bits of /user
    #	bg/fg - Lost/gained tab focus, note Google API key
    def put_user(self, buf):
	# /user
	if not self.path_match("user"):
	    return False,None




	# Focus
	approot = self.server.approot
	u = approot.users.get(self.user)
	if u is None:
	    return False,None
	if "bg" in self.vals:




























	    u.pushkeys.add(self.vals["bg"])

	elif "fg" in self.vals:
	    key = self.vals["fg"]
	    if key in u.pushkeys:
		u.pushkeys.remove(key)

	# Ok
	return True,self.send_result("", "text/html")






|













>
>
>






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#
# put.py
#	HTML PUT handling
# /user? - Update user status
#	[fg|bg] - Tab open?
#
import sys, json, socket
import chore

class PUT_mixin(object):
    def __init__(self):
	self.dispatchers.append( ("PUT", self.put_user) )

    # Set various bits of /user
    #	bg/fg - Lost/gained tab focus, note Google API key
    def put_user(self, buf):
	# /user
	if not self.path_match("user"):
	    return False,None

	# Client ID
	clientID = self.vals.get("clientID", "XXX")

	# Focus
	approot = self.server.approot
	u = approot.users.get(self.user)
	if u is None:
	    return False,None
	if "bg" in self.vals:
	    # Note hostname at which they reached us
	    # (It actually should be global, but just in case somebody's
	    #  being a jerk, it'll just hurt themselves.)
	    if u.host is None:
		u.host = self.headers["host"]

	    # Cancel their pending GET's
	    tbd = set()

	    # First trim them from the pending list
	    for tup in tuple(u.pending):
		if tup[2] == clientID:
		    tbd.add(tup)
		    u.pending.remove(tup)

	    # Now close their socket then let them exit()
	    for tup in tbd:
		handler = tup[3]
		sock = handler.connection
		handler.socket = None
		sys.stderr.write("Cancel %s %r\n" % (self.user, sock))
		sock.shutdown(socket.SHUT_RDWR)
		sock.close()

		# Let the thread wake up & exit
		tup[1].release()

	    # Register us for FCM messaging
	    u.pushkeys.add(self.vals["bg"])

	elif "fg" in self.vals:
	    key = self.vals["fg"]
	    if key in u.pushkeys:
		u.pushkeys.remove(key)

	# Ok
	return True,self.send_result("", "text/html")

Changes to user.py.

22
23
24
25
26
27
28

29
30
31
32
33
34
35

36
37
38
39
40
41
42
..
43
44
45
46
47
48
49

50
51
52
53
54
55
56
...
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
...
130
131
132
133
134
135
136
137
# active{} - Mapping from account name to (Account,Thread) serving it
# gen - Generation of content; used by client requests to detect
#	new content
# msgs[] - Current list of tuples representing messages being
#	displayed out at the browser.  There are config["nmsg"]
#	of them.  Each is (generation#, from, to, body)
# pending[] - List of clients waiting (Ajax) for new messages

# roster{} - Map from recipient to the Account they're under
# status{} - Map from account name to their status
#	(it was under the User directly, but you can get back
#	 presence messages before the user shows up in the roster)
# serial - Running counter, so clients can detect dup (from cache)
#	completions.
# pushkeys[] - Set of active Google messaging endpoints

#
class User(object):
    def __init__(self, top, user, accounts):
	self.top = top
	self.name = user
	self.accounts = accounts
	self.activity = None
................................................................................
	self.timeout = None
	self.exclusion = Exclusion()
	self.active = {}
	self.roster = {}
	self.status = {}
	self.pushkeys = set()
	self.serial = 1


	# Initial generation of content;
	#  roster current, and
	#  generation of messages.
	self.rgen = self.gen = 1

	# Always start with a welcome message
................................................................................
    # After the system sees us without a user long enough, it figures the
    #  user's gone away and tells us to clean up.
    def stop(self):
	for acct,tid in self.active:
	    acct.stop(tid)

    # An HTTP client is holding off an Ajax completion
    def await(self, timeout, sema):
	self.pending.add( (time.time() + timeout, sema) )

    # Add a new message
    # It's received (i.e., *to* us) if @rx, involving @them
    #  with body of @mBody
    def add(self, rx, them, mBody):
	mmax = self.top.config["nmsg"]
	msgs = self.msgs
................................................................................

	# Pending Google Chrome browsers
	if self.pushkeys:
	    # Grab current list and reset
	    keys = self.pushkeys
	    self.pushkeys = set()
	    for k in keys:
		self.top.fcm(k, them, mBody)







>







>







 







>







 







|
|







 







|
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
..
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
...
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
...
133
134
135
136
137
138
139
140
# active{} - Mapping from account name to (Account,Thread) serving it
# gen - Generation of content; used by client requests to detect
#	new content
# msgs[] - Current list of tuples representing messages being
#	displayed out at the browser.  There are config["nmsg"]
#	of them.  Each is (generation#, from, to, body)
# pending[] - List of clients waiting (Ajax) for new messages
#	Each is a tuple (timeout, semaphore, client-id, www_handler)
# roster{} - Map from recipient to the Account they're under
# status{} - Map from account name to their status
#	(it was under the User directly, but you can get back
#	 presence messages before the user shows up in the roster)
# serial - Running counter, so clients can detect dup (from cache)
#	completions.
# pushkeys[] - Set of active Google messaging endpoints
# host - Record host at which they reach us
#
class User(object):
    def __init__(self, top, user, accounts):
	self.top = top
	self.name = user
	self.accounts = accounts
	self.activity = None
................................................................................
	self.timeout = None
	self.exclusion = Exclusion()
	self.active = {}
	self.roster = {}
	self.status = {}
	self.pushkeys = set()
	self.serial = 1
	self.host = None

	# Initial generation of content;
	#  roster current, and
	#  generation of messages.
	self.rgen = self.gen = 1

	# Always start with a welcome message
................................................................................
    # After the system sees us without a user long enough, it figures the
    #  user's gone away and tells us to clean up.
    def stop(self):
	for acct,tid in self.active:
	    acct.stop(tid)

    # An HTTP client is holding off an Ajax completion
    def await(self, timeout, sema, clientID, handler):
	self.pending.add( (time.time() + timeout, sema, clientID, handler) )

    # Add a new message
    # It's received (i.e., *to* us) if @rx, involving @them
    #  with body of @mBody
    def add(self, rx, them, mBody):
	mmax = self.top.config["nmsg"]
	msgs = self.msgs
................................................................................

	# Pending Google Chrome browsers
	if self.pushkeys:
	    # Grab current list and reset
	    keys = self.pushkeys
	    self.pushkeys = set()
	    for k in keys:
		self.top.fcm(self.host, k, them, mBody)