Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
883c07e16c | ||
|
|
f40f4292a7 | ||
|
|
4471dfd05c | ||
|
|
327ec69b05 | ||
|
|
f569e12dbf | ||
|
|
b2bac74955 | ||
|
|
a782d7170c | ||
|
|
13ff0dc5d1 | ||
|
|
761cb92767 | ||
|
|
381283d864 | ||
|
|
f1bfb42258 | ||
|
|
fec7ee2556 | ||
|
|
cb862d787d | ||
|
|
a51359068f | ||
|
|
7ef3e5ab0c | ||
|
|
fb0d695d6e | ||
|
|
ffc597f222 | ||
|
|
cded0a2865 | ||
|
|
2be4467784 | ||
|
|
28daefd58a | ||
|
|
107b0a3132 | ||
|
|
568e1ad04f | ||
|
|
5b9dc202b7 | ||
|
|
53eea12572 | ||
|
|
87e90cc1e7 | ||
|
|
389f67c88b | ||
|
|
d3c5a0fbbd | ||
|
|
283aaa7f2a | ||
|
|
c23c19edc2 | ||
|
|
b8cc239408 | ||
|
|
b67d800bcf | ||
|
|
22ce42cc8a | ||
|
|
a9bfcb449c | ||
|
|
553b50fa2a | ||
|
|
dfc4a661ff | ||
|
|
2e36c01334 | ||
|
|
22072ed385 | ||
|
|
55b8f248ce | ||
|
|
47e174d0e7 | ||
|
|
37dc4555b7 | ||
|
|
c8c8957c15 | ||
|
|
1031f2fee9 | ||
|
|
df1beb58d9 | ||
|
|
f1a670749d | ||
|
|
34744c2a36 | ||
|
|
8d1d79ef58 | ||
|
|
2ec0402962 | ||
|
|
d6933a85e2 | ||
|
|
c15b57aa05 | ||
|
|
518f3a45af | ||
|
|
fca0da9917 | ||
|
|
e8ec10c26e | ||
|
|
f2f191f118 | ||
|
|
d5242b2c86 | ||
|
|
11010fdcd4 | ||
|
|
f8105f5d9f | ||
|
|
48085dd978 | ||
|
|
d862580b22 | ||
|
|
725c83d96f | ||
|
|
65bfdda48f | ||
|
|
f73308434f | ||
|
|
f4b18f32ec | ||
|
|
2dbc17ec98 | ||
|
|
24825f53c6 | ||
|
|
acfcee848f | ||
|
|
903fe69762 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*.pyc
|
||||
202
LICENSE
202
LICENSE
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
21
README.md
21
README.md
@@ -1,21 +0,0 @@
|
||||
cosmopolite
|
||||
===========
|
||||
|
||||
Client/server publish/subscribe and key/value storage for AppEngine.
|
||||
|
||||
Components:
|
||||
* A server API built on the AppEngine Python framework.
|
||||
* A browser client library written in JavaScript.
|
||||
|
||||
Feature overview:
|
||||
* Near-realtime notification to subscribers of messages published to a "subject"
|
||||
* Server-side storage of past messages for replay later to clients
|
||||
* Support for associating a key with a message and for lookup of the most recent
|
||||
message for a given key
|
||||
* Client identification persistence via localStorage tokens or in combination
|
||||
with Google account signin
|
||||
* Complex messages supported via transparent JSON serialization
|
||||
* Server-side strict ordering of messages
|
||||
* Client-side message queueing in localStorage and resumption on restart
|
||||
* Message duplication detection and elimination
|
||||
* Promise support for notification of client -> server operation completion
|
||||
147
api.py
147
api.py
@@ -1,147 +0,0 @@
|
||||
# Copyright 2014, Ian Gulliver
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
import webapp2
|
||||
|
||||
from google.appengine.api import channel
|
||||
from google.appengine.ext import db
|
||||
|
||||
from cosmopolite.lib import auth
|
||||
from cosmopolite.lib import models
|
||||
from cosmopolite.lib import security
|
||||
from cosmopolite.lib import session
|
||||
from cosmopolite.lib import utils
|
||||
|
||||
import config
|
||||
|
||||
|
||||
def CreateChannel(google_user, client, args):
|
||||
token = channel.create_channel(
|
||||
client_id=str(client.key()),
|
||||
duration_minutes=config.CHANNEL_DURATION_SECONDS / 60)
|
||||
events = []
|
||||
if google_user:
|
||||
events.append({
|
||||
'event_type': 'login',
|
||||
'profile': str(client.parent_key()),
|
||||
'google_user': google_user.email(),
|
||||
})
|
||||
else:
|
||||
events.append({
|
||||
'event_type': 'logout',
|
||||
'profile': str(client.parent_key()),
|
||||
})
|
||||
|
||||
return {
|
||||
'token': token,
|
||||
'events': events,
|
||||
}
|
||||
|
||||
|
||||
def SendMessage(google_user, client, args):
|
||||
subject = args['subject']
|
||||
message = args['message']
|
||||
sender_message_id = args['sender_message_id']
|
||||
key = args.get('key', None)
|
||||
|
||||
try:
|
||||
models.Subject.FindOrCreate(subject).SendMessage(
|
||||
message, client.parent_key(), sender_message_id, key)
|
||||
except models.DuplicateMessage:
|
||||
logging.exception('Duplicate message: %s', sender_message_id)
|
||||
return {
|
||||
'result': 'duplicate_message',
|
||||
}
|
||||
except models.AccessDenied:
|
||||
logging.exception('SendMessage access denied')
|
||||
return {
|
||||
'result': 'access_denied',
|
||||
}
|
||||
|
||||
return {
|
||||
'result': 'ok',
|
||||
}
|
||||
|
||||
|
||||
def Subscribe(google_user, client, args):
|
||||
subject = models.Subject.FindOrCreate(args['subject'])
|
||||
messages = args.get('messages', 0)
|
||||
last_id = args.get('last_id', None)
|
||||
keys = args.get('keys', [])
|
||||
|
||||
try:
|
||||
ret = {
|
||||
'result': 'ok',
|
||||
'events': models.Subscription.FindOrCreate(subject, client, messages, last_id),
|
||||
}
|
||||
except models.AccessDenied:
|
||||
logging.exception('Subscribe access denied')
|
||||
return {
|
||||
'result': 'access_denied',
|
||||
}
|
||||
|
||||
for key in keys:
|
||||
message = subject.GetKey(key)
|
||||
if message:
|
||||
ret['events'].append(message.ToEvent())
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def Unsubscribe(google_user, client, args):
|
||||
subject = models.Subject.FindOrCreate(args['subject'])
|
||||
models.Subscription.Remove(subject, client)
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
class APIWrapper(webapp2.RequestHandler):
|
||||
|
||||
_COMMANDS = {
|
||||
'createChannel': CreateChannel,
|
||||
'sendMessage': SendMessage,
|
||||
'subscribe': Subscribe,
|
||||
'unsubscribe': Unsubscribe,
|
||||
}
|
||||
|
||||
@utils.chaos_monkey
|
||||
@utils.expects_json
|
||||
@utils.returns_json
|
||||
@utils.local_namespace
|
||||
@security.google_user_xsrf_protection
|
||||
@security.weak_security_checks
|
||||
@session.session_required
|
||||
def post(self):
|
||||
ret = {
|
||||
'status': 'ok',
|
||||
'responses': [],
|
||||
'events': [],
|
||||
}
|
||||
for command in self.request_json['commands']:
|
||||
callback = self._COMMANDS[command['command']]
|
||||
result = callback(
|
||||
self.verified_google_user,
|
||||
self.client,
|
||||
command.get('arguments', {}))
|
||||
# Magic: if result contains "events", haul them up a level so the
|
||||
# client can see them as a single stream.
|
||||
ret['events'].extend(result.pop('events', []))
|
||||
ret['responses'].append(result)
|
||||
return ret
|
||||
|
||||
|
||||
app = webapp2.WSGIApplication([
|
||||
(config.URL_PREFIX + '/api', APIWrapper),
|
||||
])
|
||||
69
app.yaml
Normal file
69
app.yaml
Normal file
@@ -0,0 +1,69 @@
|
||||
runtime: python27
|
||||
version: 1
|
||||
api_version: 1
|
||||
application: cosmopolite-root
|
||||
threadsafe: true
|
||||
|
||||
handlers:
|
||||
- url: /
|
||||
static_files: index.html
|
||||
upload: index.html
|
||||
secure: always
|
||||
http_headers:
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||
|
||||
- url: /overview
|
||||
static_files: overview.html
|
||||
upload: overview.html
|
||||
secure: always
|
||||
http_headers:
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||
|
||||
- url: /reference
|
||||
static_files: reference.html
|
||||
upload: reference.html
|
||||
secure: always
|
||||
http_headers:
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||
|
||||
- url: /tutorial
|
||||
static_files: tutorial.html
|
||||
upload: tutorial.html
|
||||
secure: always
|
||||
http_headers:
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||
|
||||
- url: /images
|
||||
static_dir: images
|
||||
secure: always
|
||||
expiration: 7d
|
||||
http_headers:
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||
|
||||
- url: /stylesheets
|
||||
static_dir: stylesheets
|
||||
secure: always
|
||||
expiration: 7d
|
||||
http_headers:
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||
|
||||
- url: /externs
|
||||
static_dir: externs
|
||||
secure: always
|
||||
expiration: 7d
|
||||
http_headers:
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||
41
auth.py
41
auth.py
@@ -1,41 +0,0 @@
|
||||
# Copyright 2014, Ian Gulliver
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import webapp2
|
||||
|
||||
from google.appengine.api import users
|
||||
|
||||
from cosmopolite.lib import security
|
||||
|
||||
import config
|
||||
|
||||
|
||||
class Login(webapp2.RequestHandler):
|
||||
@security.weak_security_checks
|
||||
def get(self):
|
||||
self.redirect(users.create_login_url(
|
||||
dest_url=config.URL_PREFIX + '/static/login_complete.html'))
|
||||
|
||||
|
||||
class Logout(webapp2.RequestHandler):
|
||||
@security.weak_security_checks
|
||||
def get(self):
|
||||
self.redirect(users.create_logout_url(
|
||||
dest_url=config.URL_PREFIX + '/static/logout_complete.html'))
|
||||
|
||||
|
||||
app = webapp2.WSGIApplication([
|
||||
(config.URL_PREFIX + '/auth/login', Login),
|
||||
(config.URL_PREFIX + '/auth/logout', Logout),
|
||||
])
|
||||
48
channel.py
48
channel.py
@@ -1,48 +0,0 @@
|
||||
# Copyright 2014, Ian Gulliver
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import webapp2
|
||||
|
||||
from google.appengine.ext import db
|
||||
|
||||
from cosmopolite.lib import auth
|
||||
from cosmopolite.lib import models
|
||||
from cosmopolite.lib import utils
|
||||
|
||||
|
||||
class OnChannelConnect(webapp2.RequestHandler):
|
||||
@utils.local_namespace
|
||||
@db.transactional()
|
||||
def post(self):
|
||||
client = models.Client.get(self.request.get('from'))
|
||||
client.channel_active = True
|
||||
client.put()
|
||||
|
||||
|
||||
class OnChannelDisconnect(webapp2.RequestHandler):
|
||||
@utils.local_namespace
|
||||
def post(self):
|
||||
client = models.Client.get(self.request.get('from'))
|
||||
client.channel_active = False
|
||||
client.put()
|
||||
|
||||
subscriptions = models.Subscription.all().filter('client =', client)
|
||||
for subscription in subscriptions:
|
||||
subscription.delete()
|
||||
|
||||
|
||||
app = webapp2.WSGIApplication([
|
||||
('/_ah/channel/connected/', OnChannelConnect),
|
||||
('/_ah/channel/disconnected/', OnChannelDisconnect),
|
||||
])
|
||||
@@ -1,9 +0,0 @@
|
||||
# Co-existence
|
||||
URL_PREFIX = '/cosmopolite'
|
||||
NAMESPACE = 'cosmopolite'
|
||||
|
||||
# Timings
|
||||
CHANNEL_DURATION_SECONDS = 60 * 60 * 2 # 2 hours
|
||||
|
||||
# Probabilities
|
||||
CHAOS_PROBABILITY = 0.50
|
||||
215
externs/cosmopolite.js
Normal file
215
externs/cosmopolite.js
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* @fileoverview Definitions for Cosmopolite API. Details of the API are at:
|
||||
* https://www.cosmopolite.org/reference
|
||||
*
|
||||
* @externs
|
||||
* @author ian@cosmopolite.org (Ian Gulliver)
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return {number}
|
||||
*/
|
||||
String.prototype.hashCode = function() {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#message
|
||||
* @typedef {{event_type: string,
|
||||
* id: number,
|
||||
* created: number,
|
||||
* sender: string,
|
||||
* subject: Cosmopolite.typeSubject,
|
||||
* message: *}}
|
||||
*/
|
||||
Cosmopolite.typeMessage;
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#subject
|
||||
* @typedef {{name: string,
|
||||
* readable_only_by: (string|undefined),
|
||||
* writable_only_by: (string|undefined)}}
|
||||
*/
|
||||
Cosmopolite.typeSubject;
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#subject
|
||||
* @typedef {(Cosmopolite.typeSubject|string|number)}
|
||||
*/
|
||||
Cosmopolite.typeSubjectLoose;
|
||||
|
||||
|
||||
/**
|
||||
* @see https://cosmopolite.org/reference#logout
|
||||
* @typedef {{login_url: string}}
|
||||
*/
|
||||
Cosmopolite.typeEventLogoutDetail;
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#constructor
|
||||
* @constructor
|
||||
* @param {?string=} opt_urlPrefix
|
||||
* @param {?string=} opt_namespace
|
||||
* @param {?string=} opt_trackingID
|
||||
* @nosideeffects
|
||||
*/
|
||||
function Cosmopolite(opt_urlPrefix, opt_namespace, opt_trackingID) {}
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#uuid
|
||||
* @return {string}
|
||||
* @nosideeffects
|
||||
*/
|
||||
Cosmopolite.uuid = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#shutdown
|
||||
*/
|
||||
Cosmopolite.prototype.shutdown = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
|
||||
* @param {string} type
|
||||
* @param {function(Event)} listener
|
||||
*/
|
||||
Cosmopolite.prototype.addEventListener = function(type, listener) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
|
||||
* @param {string} type
|
||||
* @param {function(Event)} listener
|
||||
*/
|
||||
Cosmopolite.prototype.removeEventListener = function(type, listener) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent
|
||||
* @param {!Event} event
|
||||
* @return {boolean}
|
||||
*/
|
||||
Cosmopolite.prototype.dispatchEvent = function(event) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#connected
|
||||
* @return {boolean}
|
||||
* @nosideeffects
|
||||
*/
|
||||
Cosmopolite.prototype.connected = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#getProfile
|
||||
* @return {Promise}
|
||||
* @nosideeffects
|
||||
*/
|
||||
Cosmopolite.prototype.getProfile = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#currentProfile
|
||||
* @const
|
||||
* @nosideeffects
|
||||
*/
|
||||
Cosmopolite.prototype.currentProfile = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#subscribe
|
||||
* @param {Cosmopolite.typeSubjectLoose|Array.<Cosmopolite.typeSubjectLoose>}
|
||||
* subjects
|
||||
* @param {?number=} opt_messages
|
||||
* @param {?number=} opt_lastID
|
||||
* @return {Promise|Array.<Promise>}
|
||||
*/
|
||||
Cosmopolite.prototype.subscribe =
|
||||
function(subjects, opt_messages, opt_lastID) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#unsubscribe
|
||||
* @param {Cosmopolite.typeSubjectLoose} subject
|
||||
* @return {Promise}
|
||||
*/
|
||||
Cosmopolite.prototype.unsubscribe = function(subject) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#sendMessage
|
||||
* @param {Cosmopolite.typeSubjectLoose} subject
|
||||
* @param {!*} message
|
||||
* @return {Promise}
|
||||
*/
|
||||
Cosmopolite.prototype.sendMessage = function(subject, message) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#getMessages
|
||||
* @param {Cosmopolite.typeSubjectLoose} subject
|
||||
* @return {Array.<Cosmopolite.typeMessage>}
|
||||
* @const
|
||||
* @nosideeffects
|
||||
*/
|
||||
Cosmopolite.prototype.getMessages = function(subject) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#getLastMessage
|
||||
* @param {Cosmopolite.typeSubjectLoose} subject
|
||||
* @return {?Cosmopolite.typeMessage}
|
||||
* @const
|
||||
* @nosideeffects
|
||||
*/
|
||||
Cosmopolite.prototype.getLastMessage = function(subject) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#pin_method
|
||||
* @param {Cosmopolite.typeSubjectLoose} subject
|
||||
* @param {!*} message
|
||||
* @return {Promise}
|
||||
*/
|
||||
Cosmopolite.prototype.pin = function(subject, message) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#unpin
|
||||
* @param {string} id
|
||||
* @return {Promise}
|
||||
*/
|
||||
Cosmopolite.prototype.unpin = function(id) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#getPins
|
||||
* @param {Cosmopolite.typeSubjectLoose} subject
|
||||
* @return {Array.<Cosmopolite.typeMessage>}
|
||||
* @const
|
||||
* @nosideeffects
|
||||
*/
|
||||
Cosmopolite.prototype.getPins = function(subject) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#trackEvent
|
||||
* @param {...*} var_args
|
||||
*/
|
||||
Cosmopolite.prototype.trackEvent = function(var_args) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see https://www.cosmopolite.org/reference#uuid
|
||||
* @return {string}
|
||||
* @const
|
||||
* @nosideeffects
|
||||
*/
|
||||
Cosmopolite.prototype.uuid = function() {};
|
||||
110
externs/hogfather.js
Normal file
110
externs/hogfather.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @fileoverview Definitions for Cosmopolite API. Details of the API are at:
|
||||
* https://www.cosmopolite.org/hogfather/reference
|
||||
*
|
||||
* @externs
|
||||
* @author ian@cosmopolite.org (Ian Gulliver)
|
||||
*/
|
||||
|
||||
|
||||
/* Namespace */
|
||||
var hogfather = {};
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Cosmopolite} cosmo
|
||||
* @param {string} id
|
||||
* @private
|
||||
*/
|
||||
hogfather.PublicChat = function(cosmo, id) {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {Cosmopolite} cosmo
|
||||
* @return {Promise}
|
||||
*/
|
||||
hogfather.PublicChat.create = function(cosmo) {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {Cosmopolite} cosmo
|
||||
* @param {string} id
|
||||
* @return {Promise}
|
||||
*/
|
||||
hogfather.PublicChat.join = function(cosmo, id) {};
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
hogfather.PublicChat.prototype.shutdown = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
* @nosideeffects
|
||||
*/
|
||||
hogfather.PublicChat.prototype.getID = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @nosideeffects
|
||||
*/
|
||||
hogfather.PublicChat.prototype.amOwner = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @nosideeffects
|
||||
*/
|
||||
hogfather.PublicChat.prototype.amWriter = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Array.<Cosmopolite.typeMessage>}
|
||||
* @nosideeffects
|
||||
*/
|
||||
hogfather.PublicChat.prototype.getMessages = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Array.<Cosmopolite.typeMessage>}
|
||||
* @nosideeffects
|
||||
*/
|
||||
hogfather.PublicChat.prototype.getRequests = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!*} message
|
||||
* @return {Promise}
|
||||
*/
|
||||
hogfather.PublicChat.prototype.sendMessage = function(message) {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} info
|
||||
* @return {Promise}
|
||||
*/
|
||||
hogfather.PublicChat.prototype.requestAccess = function(info) {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} sender
|
||||
* @return {Promise}
|
||||
*/
|
||||
hogfather.PublicChat.prototype.addOwner = function(sender) {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} sender
|
||||
* @return {Promise}
|
||||
*/
|
||||
hogfather.PublicChat.prototype.addWriter = function(sender) {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} sender
|
||||
* @return {Promise}
|
||||
*/
|
||||
hogfather.PublicChat.prototype.denyRequest = function(sender) {};
|
||||
623
images/architecture.svg
Normal file
623
images/architecture.svg
Normal file
@@ -0,0 +1,623 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="16.9 174.7 319.6 236.6" enable-background="new 16.9 174.7 319.6 236.6" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#CFE2F3" d="M17.9,175.7h317.6v125.2H17.9L17.9,175.7z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="round" stroke-miterlimit="10" d="M17.9,175.7h317.6v125.2
|
||||
H17.9L17.9,175.7z"/>
|
||||
<path d="M130.1,189.5v-1h3.7v3.2c-0.6,0.5-1.1,0.8-1.8,1c-0.6,0.2-1.2,0.3-1.8,0.3c-0.8,0-1.6-0.2-2.3-0.5
|
||||
c-0.7-0.4-1.2-0.9-1.6-1.6c-0.4-0.7-0.5-1.5-0.5-2.3c0-0.8,0.2-1.6,0.5-2.4c0.4-0.7,0.9-1.3,1.5-1.6c0.7-0.4,1.4-0.5,2.3-0.5
|
||||
c0.6,0,1.2,0.1,1.7,0.3c0.5,0.2,0.9,0.5,1.2,0.8c0.3,0.4,0.5,0.8,0.7,1.4l-1,0.3c-0.1-0.4-0.3-0.8-0.5-1c-0.2-0.3-0.5-0.5-0.8-0.6
|
||||
c-0.4-0.2-0.8-0.2-1.2-0.2c-0.5,0-1,0.1-1.4,0.2c-0.4,0.2-0.7,0.4-0.9,0.6c-0.2,0.3-0.4,0.5-0.5,0.9c-0.2,0.5-0.3,1.1-0.3,1.7
|
||||
c0,0.8,0.1,1.4,0.4,1.9c0.3,0.5,0.7,0.9,1.2,1.2c0.5,0.3,1.1,0.4,1.6,0.4c0.5,0,1-0.1,1.5-0.3c0.5-0.2,0.8-0.4,1.1-0.6v-1.6H130.1
|
||||
z M134.7,189.7c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9
|
||||
c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8C135,191.6,134.7,190.8,134.7,189.7z M135.8,189.7
|
||||
c0,0.8,0.2,1.4,0.5,1.8c0.4,0.4,0.8,0.6,1.3,0.6s1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8
|
||||
c-0.4-0.4-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6C136,188.3,135.8,188.9,135.8,189.7z M141.4,189.7c0-1.2,0.3-2,1-2.6
|
||||
c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9c-0.2,0.5-0.6,0.8-1.1,1.1
|
||||
c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8C141.6,191.6,141.4,190.8,141.4,189.7z M142.4,189.7c0,0.8,0.2,1.4,0.5,1.8
|
||||
c0.4,0.4,0.8,0.6,1.3,0.6s1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8c-0.4-0.4-0.8-0.6-1.3-0.6
|
||||
c-0.5,0-1,0.2-1.3,0.6C142.6,188.3,142.4,188.9,142.4,189.7z M148.2,193.4l1,0.2c0,0.3,0.2,0.6,0.4,0.7c0.3,0.2,0.6,0.3,1.1,0.3
|
||||
c0.5,0,0.9-0.1,1.1-0.3c0.3-0.2,0.4-0.5,0.5-0.8c0.1-0.2,0.1-0.7,0.1-1.4c-0.5,0.6-1,0.8-1.7,0.8c-0.9,0-1.5-0.3-2-0.9
|
||||
c-0.5-0.6-0.7-1.4-0.7-2.2c0-0.6,0.1-1.1,0.3-1.7c0.2-0.5,0.5-0.9,0.9-1.2c0.4-0.3,0.9-0.4,1.4-0.4c0.7,0,1.4,0.3,1.8,0.9v-0.8h1
|
||||
v5.4c0,1-0.1,1.7-0.3,2.1c-0.2,0.4-0.5,0.7-0.9,1c-0.4,0.2-1,0.4-1.6,0.4c-0.8,0-1.4-0.2-1.8-0.5
|
||||
C148.4,194.6,148.2,194.1,148.2,193.4z M149.1,189.6c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6c0.5,0,0.9-0.2,1.2-0.6
|
||||
s0.5-1,0.5-1.8c0-0.8-0.2-1.3-0.5-1.7c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6C149.2,188.3,149.1,188.9,149.1,189.6z
|
||||
M155,192.9v-8.7h1.1v8.7H155z M161.9,190.9l1.1,0.1c-0.2,0.6-0.5,1.1-1,1.5c-0.5,0.4-1.1,0.5-1.8,0.5c-0.9,0-1.6-0.3-2.2-0.8
|
||||
c-0.5-0.6-0.8-1.4-0.8-2.4c0-1.1,0.3-1.9,0.8-2.5c0.5-0.6,1.3-0.9,2.1-0.9c0.8,0,1.5,0.3,2.1,0.9c0.5,0.6,0.8,1.4,0.8,2.4
|
||||
c0,0.1,0,0.2,0,0.3h-4.7c0,0.7,0.2,1.2,0.6,1.6s0.8,0.5,1.3,0.5c0.4,0,0.7-0.1,1-0.3C161.5,191.6,161.8,191.3,161.9,190.9z
|
||||
M158.4,189.1h3.5c0-0.5-0.2-0.9-0.4-1.2c-0.3-0.4-0.8-0.6-1.3-0.6c-0.5,0-0.9,0.2-1.2,0.5C158.6,188.1,158.5,188.6,158.4,189.1z
|
||||
M166.7,192.9l3.3-8.7h1.2l3.5,8.7h-1.3l-1-2.6h-3.6l-0.9,2.6H166.7z M169.2,189.3h2.9l-0.9-2.4c-0.3-0.7-0.5-1.3-0.6-1.8
|
||||
c-0.1,0.6-0.3,1.1-0.5,1.7L169.2,189.3z M175.5,195.3v-8.7h1v0.8c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.6-0.2,1-0.2
|
||||
c0.5,0,1,0.1,1.4,0.4c0.4,0.3,0.7,0.7,0.9,1.2c0.2,0.5,0.3,1.1,0.3,1.7c0,0.6-0.1,1.2-0.3,1.7c-0.2,0.5-0.6,0.9-1,1.2
|
||||
s-0.9,0.4-1.4,0.4c-0.4,0-0.7-0.1-1-0.2c-0.3-0.2-0.5-0.3-0.7-0.6v3.1H175.5z M176.4,189.8c0,0.8,0.2,1.4,0.5,1.8
|
||||
c0.3,0.4,0.7,0.6,1.2,0.6c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.9c0-0.8-0.2-1.4-0.5-1.8c-0.3-0.4-0.7-0.6-1.2-0.6
|
||||
c-0.5,0-0.9,0.2-1.2,0.6C176.6,188.3,176.4,189,176.4,189.8z M182.1,195.3v-8.7h1v0.8c0.2-0.3,0.5-0.6,0.8-0.7
|
||||
c0.3-0.2,0.6-0.2,1-0.2c0.5,0,1,0.1,1.4,0.4c0.4,0.3,0.7,0.7,0.9,1.2c0.2,0.5,0.3,1.1,0.3,1.7c0,0.6-0.1,1.2-0.3,1.7
|
||||
c-0.2,0.5-0.6,0.9-1,1.2s-0.9,0.4-1.4,0.4c-0.4,0-0.7-0.1-1-0.2c-0.3-0.2-0.5-0.3-0.7-0.6v3.1H182.1z M183.1,189.8
|
||||
c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.9c0-0.8-0.2-1.4-0.5-1.8
|
||||
c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6C183.2,188.3,183.1,189,183.1,189.8z M192.2,192.9v-8.7h6.3v1h-5.1v2.7h4.8v1
|
||||
h-4.8v2.9h5.3v1H192.2z M199.9,192.9v-6.3h1v0.9c0.5-0.7,1.1-1,2-1c0.4,0,0.7,0.1,1,0.2c0.3,0.1,0.6,0.3,0.7,0.5
|
||||
c0.2,0.2,0.3,0.5,0.3,0.8c0,0.2,0.1,0.5,0.1,1v3.9H204V189c0-0.4,0-0.8-0.1-1c-0.1-0.2-0.2-0.4-0.4-0.5c-0.2-0.1-0.5-0.2-0.7-0.2
|
||||
c-0.5,0-0.8,0.1-1.2,0.4c-0.3,0.3-0.5,0.8-0.5,1.6v3.4H199.9z M206.4,193.4l1,0.2c0,0.3,0.2,0.6,0.4,0.7c0.3,0.2,0.6,0.3,1.1,0.3
|
||||
c0.5,0,0.9-0.1,1.1-0.3c0.3-0.2,0.4-0.5,0.5-0.8c0.1-0.2,0.1-0.7,0.1-1.4c-0.5,0.6-1,0.8-1.7,0.8c-0.9,0-1.5-0.3-2-0.9
|
||||
c-0.5-0.6-0.7-1.4-0.7-2.2c0-0.6,0.1-1.1,0.3-1.7c0.2-0.5,0.5-0.9,0.9-1.2c0.4-0.3,0.9-0.4,1.4-0.4c0.7,0,1.4,0.3,1.8,0.9v-0.8h1
|
||||
v5.4c0,1-0.1,1.7-0.3,2.1c-0.2,0.4-0.5,0.7-0.9,1c-0.4,0.2-1,0.4-1.6,0.4c-0.8,0-1.4-0.2-1.8-0.5
|
||||
C206.6,194.6,206.3,194.1,206.4,193.4z M207.2,189.6c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6c0.5,0,0.9-0.2,1.2-0.6
|
||||
c0.3-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.3-0.5-1.7c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6
|
||||
C207.4,188.3,207.2,188.9,207.2,189.6z M213.2,185.4v-1.2h1.1v1.2H213.2z M213.2,192.9v-6.3h1.1v6.3H213.2z M215.8,192.9v-6.3h1
|
||||
v0.9c0.5-0.7,1.1-1,2-1c0.4,0,0.7,0.1,1,0.2c0.3,0.1,0.6,0.3,0.7,0.5c0.2,0.2,0.3,0.5,0.3,0.8c0,0.2,0.1,0.5,0.1,1v3.9h-1.1V189
|
||||
c0-0.4,0-0.8-0.1-1c-0.1-0.2-0.2-0.4-0.4-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.5,0-0.8,0.1-1.2,0.4c-0.3,0.3-0.5,0.8-0.5,1.6v3.4
|
||||
H215.8z M226.7,190.9l1.1,0.1c-0.2,0.6-0.5,1.1-1,1.5c-0.5,0.4-1.1,0.5-1.8,0.5c-0.9,0-1.6-0.3-2.2-0.8s-0.8-1.4-0.8-2.4
|
||||
c0-1.1,0.3-1.9,0.8-2.5c0.5-0.6,1.3-0.9,2.1-0.9c0.8,0,1.5,0.3,2.1,0.9c0.5,0.6,0.8,1.4,0.8,2.4c0,0.1,0,0.2,0,0.3h-4.7
|
||||
c0,0.7,0.2,1.2,0.6,1.6s0.8,0.5,1.3,0.5c0.4,0,0.7-0.1,1-0.3C226.3,191.6,226.6,191.3,226.7,190.9z M223.2,189.1h3.5
|
||||
c0-0.5-0.2-0.9-0.4-1.2c-0.3-0.4-0.8-0.6-1.3-0.6c-0.5,0-0.9,0.2-1.2,0.5C223.4,188.1,223.3,188.6,223.2,189.1z"/>
|
||||
<path fill="#E6B8AF" d="M35.2,207.1h221.6v83.4H35.2L35.2,207.1z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="round" stroke-miterlimit="10" d="M35.2,207.1h221.6v83.4
|
||||
H35.2L35.2,207.1z"/>
|
||||
<path d="M84.5,224.3l3.3-8.7h1.2l3.5,8.7h-1.3l-1-2.6h-3.6l-0.9,2.6H84.5z M87,220.7H90l-0.9-2.4c-0.3-0.7-0.5-1.3-0.6-1.8
|
||||
c-0.1,0.6-0.3,1.1-0.5,1.7L87,220.7z M93.3,226.7V218h1v0.8c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.6-0.2,1-0.2c0.5,0,1,0.1,1.4,0.4
|
||||
c0.4,0.3,0.7,0.7,0.9,1.2c0.2,0.5,0.3,1.1,0.3,1.7c0,0.6-0.1,1.2-0.3,1.7c-0.2,0.5-0.6,0.9-1,1.2c-0.4,0.3-0.9,0.4-1.4,0.4
|
||||
c-0.4,0-0.7-0.1-1-0.2c-0.3-0.2-0.5-0.3-0.7-0.6v3.1H93.3z M94.2,221.2c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6
|
||||
c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.9c0-0.8-0.2-1.4-0.5-1.8c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6
|
||||
C94.4,219.8,94.2,220.4,94.2,221.2z M99.9,226.7V218h1v0.8c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.6-0.2,1-0.2c0.5,0,1,0.1,1.4,0.4
|
||||
c0.4,0.3,0.7,0.7,0.9,1.2c0.2,0.5,0.3,1.1,0.3,1.7c0,0.6-0.1,1.2-0.3,1.7c-0.2,0.5-0.6,0.9-1,1.2c-0.4,0.3-0.9,0.4-1.4,0.4
|
||||
c-0.4,0-0.7-0.1-1-0.2c-0.3-0.2-0.5-0.3-0.7-0.6v3.1H99.9z M100.9,221.2c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6
|
||||
c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.9c0-0.8-0.2-1.4-0.5-1.8c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6
|
||||
C101,219.8,100.9,220.4,100.9,221.2z M110,224.3v-8.7h6.3v1h-5.1v2.7h4.8v1h-4.8v2.9h5.3v1H110z M117.7,224.3V218h1v0.9
|
||||
c0.5-0.7,1.1-1,2-1c0.4,0,0.7,0.1,1,0.2c0.3,0.1,0.6,0.3,0.7,0.5c0.2,0.2,0.3,0.5,0.3,0.8c0,0.2,0.1,0.5,0.1,1v3.9h-1.1v-3.8
|
||||
c0-0.4,0-0.8-0.1-1c-0.1-0.2-0.2-0.4-0.4-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.5,0-0.8,0.1-1.2,0.4c-0.3,0.3-0.5,0.8-0.5,1.6v3.4
|
||||
H117.7z M124.2,224.8l1,0.2c0,0.3,0.2,0.6,0.4,0.7c0.3,0.2,0.6,0.3,1.1,0.3c0.5,0,0.9-0.1,1.1-0.3c0.3-0.2,0.4-0.5,0.5-0.8
|
||||
c0.1-0.2,0.1-0.7,0.1-1.4c-0.5,0.6-1,0.8-1.7,0.8c-0.9,0-1.5-0.3-2-0.9c-0.5-0.6-0.7-1.4-0.7-2.2c0-0.6,0.1-1.1,0.3-1.7
|
||||
c0.2-0.5,0.5-0.9,0.9-1.2c0.4-0.3,0.9-0.4,1.4-0.4c0.7,0,1.4,0.3,1.8,0.9V218h1v5.4c0,1-0.1,1.7-0.3,2.1c-0.2,0.4-0.5,0.7-0.9,1
|
||||
c-0.4,0.2-1,0.4-1.6,0.4c-0.8,0-1.4-0.2-1.8-0.5C124.4,226,124.2,225.5,124.2,224.8z M125,221c0,0.8,0.2,1.4,0.5,1.8
|
||||
c0.3,0.4,0.7,0.6,1.2,0.6c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.3-0.5-1.7c-0.3-0.4-0.7-0.6-1.2-0.6
|
||||
c-0.5,0-0.9,0.2-1.2,0.6C125.2,219.7,125,220.3,125,221z M131,216.9v-1.2h1.1v1.2H131z M131,224.3V218h1.1v6.3H131z M133.6,224.3
|
||||
V218h1v0.9c0.5-0.7,1.1-1,2-1c0.4,0,0.7,0.1,1,0.2c0.3,0.1,0.6,0.3,0.7,0.5c0.2,0.2,0.3,0.5,0.3,0.8c0,0.2,0.1,0.5,0.1,1v3.9h-1.1
|
||||
v-3.8c0-0.4,0-0.8-0.1-1c-0.1-0.2-0.2-0.4-0.4-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.5,0-0.8,0.1-1.2,0.4c-0.3,0.3-0.5,0.8-0.5,1.6v3.4
|
||||
H133.6z M144.5,222.3l1.1,0.1c-0.2,0.6-0.5,1.1-1,1.5c-0.5,0.4-1.1,0.5-1.8,0.5c-0.9,0-1.6-0.3-2.2-0.8c-0.5-0.6-0.8-1.4-0.8-2.4
|
||||
c0-1.1,0.3-1.9,0.8-2.5c0.5-0.6,1.3-0.9,2.1-0.9c0.8,0,1.5,0.3,2.1,0.9c0.5,0.6,0.8,1.4,0.8,2.4c0,0.1,0,0.2,0,0.3H141
|
||||
c0,0.7,0.2,1.2,0.6,1.6s0.8,0.5,1.3,0.5c0.4,0,0.7-0.1,1-0.3C144.1,223.1,144.4,222.7,144.5,222.3z M141,220.6h3.5
|
||||
c0-0.5-0.2-0.9-0.4-1.2c-0.3-0.4-0.8-0.6-1.3-0.6c-0.5,0-0.9,0.2-1.2,0.5C141.3,219.6,141.1,220,141,220.6z M149.3,224.3l3.3-8.7
|
||||
h1.2l3.5,8.7h-1.3l-1-2.6h-3.6l-0.9,2.6H149.3z M151.8,220.7h2.9l-0.9-2.4c-0.3-0.7-0.5-1.3-0.6-1.8c-0.1,0.6-0.3,1.1-0.5,1.7
|
||||
L151.8,220.7z M158.1,226.7V218h1v0.8c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.6-0.2,1-0.2c0.5,0,1,0.1,1.4,0.4
|
||||
c0.4,0.3,0.7,0.7,0.9,1.2c0.2,0.5,0.3,1.1,0.3,1.7c0,0.6-0.1,1.2-0.3,1.7c-0.2,0.5-0.6,0.9-1,1.2c-0.4,0.3-0.9,0.4-1.4,0.4
|
||||
c-0.4,0-0.7-0.1-1-0.2c-0.3-0.2-0.5-0.3-0.7-0.6v3.1H158.1z M159.1,221.2c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6
|
||||
c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.9c0-0.8-0.2-1.4-0.5-1.8c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6
|
||||
C159.2,219.8,159.1,220.4,159.1,221.2z M164.7,226.7V218h1v0.8c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.6-0.2,1-0.2
|
||||
c0.5,0,1,0.1,1.4,0.4c0.4,0.3,0.7,0.7,0.9,1.2c0.2,0.5,0.3,1.1,0.3,1.7c0,0.6-0.1,1.2-0.3,1.7c-0.2,0.5-0.6,0.9-1,1.2
|
||||
c-0.4,0.3-0.9,0.4-1.4,0.4c-0.4,0-0.7-0.1-1-0.2c-0.3-0.2-0.5-0.3-0.7-0.6v3.1H164.7z M165.7,221.2c0,0.8,0.2,1.4,0.5,1.8
|
||||
c0.3,0.4,0.7,0.6,1.2,0.6c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.9c0-0.8-0.2-1.4-0.5-1.8c-0.3-0.4-0.7-0.6-1.2-0.6
|
||||
c-0.5,0-0.9,0.2-1.2,0.6C165.8,219.8,165.7,220.4,165.7,221.2z M171.3,224.3v-8.7h1.1v8.7H171.3z M174,216.9v-1.2h1.1v1.2H174z
|
||||
M174,224.3V218h1.1v6.3H174z M180.7,222l1,0.1c-0.1,0.7-0.4,1.3-0.9,1.7c-0.5,0.4-1.1,0.6-1.7,0.6c-0.9,0-1.5-0.3-2.1-0.8
|
||||
c-0.5-0.6-0.8-1.4-0.8-2.4c0-0.7,0.1-1.3,0.3-1.8c0.2-0.5,0.6-0.9,1-1.1c0.5-0.3,1-0.4,1.5-0.4c0.7,0,1.2,0.2,1.7,0.5
|
||||
c0.4,0.3,0.7,0.8,0.8,1.5l-1,0.2c-0.1-0.4-0.3-0.7-0.5-1c-0.3-0.2-0.6-0.3-0.9-0.3c-0.5,0-1,0.2-1.3,0.6c-0.3,0.4-0.5,1-0.5,1.8
|
||||
c0,0.8,0.2,1.5,0.5,1.8c0.3,0.4,0.7,0.6,1.3,0.6c0.4,0,0.8-0.1,1-0.4C180.4,222.9,180.6,222.5,180.7,222z M186.6,223.5
|
||||
c-0.4,0.3-0.8,0.6-1.1,0.7c-0.4,0.1-0.8,0.2-1.2,0.2c-0.7,0-1.2-0.2-1.6-0.5c-0.4-0.3-0.6-0.8-0.6-1.3c0-0.3,0.1-0.6,0.2-0.8
|
||||
c0.1-0.3,0.3-0.5,0.5-0.6c0.2-0.2,0.5-0.3,0.8-0.3c0.2-0.1,0.5-0.1,0.9-0.2c0.9-0.1,1.5-0.2,1.9-0.4c0-0.1,0-0.2,0-0.3
|
||||
c0-0.4-0.1-0.7-0.3-0.9c-0.3-0.2-0.7-0.4-1.2-0.4c-0.5,0-0.9,0.1-1.1,0.3c-0.2,0.2-0.4,0.5-0.5,0.9l-1-0.1
|
||||
c0.1-0.4,0.3-0.8,0.5-1.1c0.2-0.3,0.5-0.5,0.9-0.6c0.4-0.2,0.9-0.2,1.4-0.2c0.5,0,1,0.1,1.3,0.2c0.3,0.1,0.6,0.3,0.7,0.5
|
||||
s0.3,0.4,0.3,0.7c0,0.2,0.1,0.5,0.1,1v1.4c0,1,0,1.6,0.1,1.9c0,0.3,0.1,0.5,0.3,0.7h-1.1C186.7,224.1,186.7,223.8,186.6,223.5z
|
||||
M186.6,221.2c-0.4,0.2-1,0.3-1.7,0.4c-0.4,0.1-0.7,0.1-0.9,0.2c-0.2,0.1-0.3,0.2-0.4,0.3c-0.1,0.2-0.1,0.3-0.1,0.5
|
||||
c0,0.3,0.1,0.5,0.3,0.7c0.2,0.2,0.5,0.3,0.9,0.3c0.4,0,0.8-0.1,1.1-0.3c0.3-0.2,0.6-0.4,0.7-0.7c0.1-0.2,0.2-0.6,0.2-1.1V221.2z
|
||||
M191.5,223.3l0.1,0.9c-0.3,0.1-0.6,0.1-0.8,0.1c-0.4,0-0.7-0.1-0.9-0.2c-0.2-0.1-0.4-0.3-0.4-0.5c-0.1-0.2-0.1-0.6-0.1-1.3v-3.6
|
||||
h-0.8V218h0.8v-1.6l1.1-0.6v2.2h1.1v0.8h-1.1v3.7c0,0.3,0,0.5,0,0.6c0,0.1,0.1,0.2,0.2,0.2c0.1,0.1,0.2,0.1,0.4,0.1
|
||||
C191.1,223.4,191.3,223.4,191.5,223.3z M192.5,216.9v-1.2h1.1v1.2H192.5z M192.5,224.3V218h1.1v6.3H192.5z M194.7,221.2
|
||||
c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9
|
||||
c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8C195,223,194.7,222.2,194.7,221.2z M195.8,221.2
|
||||
c0,0.8,0.2,1.4,0.5,1.8c0.4,0.4,0.8,0.6,1.3,0.6c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8
|
||||
c-0.4-0.4-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6C196,219.8,195.8,220.4,195.8,221.2z M201.7,224.3V218h1v0.9c0.5-0.7,1.1-1,2-1
|
||||
c0.4,0,0.7,0.1,1,0.2c0.3,0.1,0.6,0.3,0.7,0.5c0.2,0.2,0.3,0.5,0.3,0.8c0,0.2,0.1,0.5,0.1,1v3.9h-1.1v-3.8c0-0.4,0-0.8-0.1-1
|
||||
c-0.1-0.2-0.2-0.4-0.4-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.5,0-0.8,0.1-1.2,0.4c-0.3,0.3-0.5,0.8-0.5,1.6v3.4H201.7z"/>
|
||||
<path fill="#FFF2CC" d="M158.1,239.6h86.8v43.4h-86.8V239.6z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="round" stroke-miterlimit="10" d="M158.1,239.6h86.8v43.4
|
||||
h-86.8V239.6z"/>
|
||||
<path d="M175.6,253.7l1.1,0.3c-0.2,0.9-0.7,1.7-1.3,2.2c-0.6,0.5-1.4,0.7-2.3,0.7c-0.9,0-1.7-0.2-2.3-0.6c-0.6-0.4-1-0.9-1.3-1.7
|
||||
c-0.3-0.7-0.5-1.5-0.5-2.3c0-0.9,0.2-1.7,0.5-2.4c0.3-0.7,0.8-1.2,1.5-1.5c0.6-0.4,1.3-0.5,2.1-0.5c0.9,0,1.6,0.2,2.2,0.7
|
||||
c0.6,0.4,1,1.1,1.2,1.9l-1.1,0.3c-0.2-0.6-0.5-1.1-0.9-1.4c-0.4-0.3-0.9-0.4-1.4-0.4c-0.7,0-1.2,0.2-1.7,0.5
|
||||
c-0.4,0.3-0.8,0.8-0.9,1.3c-0.2,0.5-0.3,1.1-0.3,1.7c0,0.7,0.1,1.4,0.3,1.9c0.2,0.5,0.6,1,1,1.2c0.5,0.3,0.9,0.4,1.5,0.4
|
||||
c0.6,0,1.2-0.2,1.6-0.5C175.2,255,175.5,254.4,175.6,253.7z M177.5,253.6c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7
|
||||
c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4
|
||||
c-0.9,0-1.6-0.3-2.1-0.8C177.7,255.5,177.5,254.6,177.5,253.6z M178.6,253.6c0,0.8,0.2,1.4,0.5,1.8c0.4,0.4,0.8,0.6,1.3,0.6
|
||||
c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8c-0.4-0.4-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6
|
||||
C178.7,252.2,178.6,252.8,178.6,253.6z M184.1,254.9l1.1-0.2c0.1,0.4,0.2,0.8,0.5,1c0.3,0.2,0.7,0.3,1.1,0.3
|
||||
c0.5,0,0.9-0.1,1.1-0.3c0.2-0.2,0.4-0.4,0.4-0.7c0-0.2-0.1-0.4-0.3-0.6c-0.1-0.1-0.5-0.2-1.1-0.4c-0.8-0.2-1.3-0.4-1.6-0.5
|
||||
c-0.3-0.1-0.5-0.3-0.7-0.6c-0.2-0.3-0.2-0.5-0.2-0.8c0-0.3,0.1-0.5,0.2-0.8c0.1-0.2,0.3-0.4,0.5-0.6c0.2-0.1,0.4-0.2,0.7-0.3
|
||||
c0.3-0.1,0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.3,0.2c0.4,0.1,0.6,0.3,0.8,0.6c0.2,0.2,0.3,0.6,0.4,1l-1,0.1c0-0.3-0.2-0.6-0.4-0.7
|
||||
c-0.2-0.2-0.5-0.3-1-0.3c-0.5,0-0.8,0.1-1,0.2c-0.2,0.2-0.3,0.3-0.3,0.6c0,0.1,0,0.3,0.1,0.4c0.1,0.1,0.2,0.2,0.4,0.3
|
||||
c0.1,0,0.4,0.1,0.9,0.3c0.8,0.2,1.3,0.4,1.6,0.5c0.3,0.1,0.5,0.3,0.7,0.6c0.2,0.2,0.3,0.6,0.3,0.9c0,0.4-0.1,0.7-0.3,1
|
||||
c-0.2,0.3-0.5,0.6-0.9,0.7c-0.4,0.2-0.8,0.2-1.3,0.2c-0.8,0-1.4-0.2-1.9-0.5C184.5,256,184.2,255.5,184.1,254.9z M190.4,256.7
|
||||
v-6.3h1v0.9c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.7-0.3,1.1-0.3c0.5,0,0.8,0.1,1.1,0.3c0.3,0.2,0.5,0.5,0.6,0.8
|
||||
c0.5-0.7,1.1-1.1,1.9-1.1c0.6,0,1.1,0.2,1.4,0.5c0.3,0.3,0.5,0.9,0.5,1.6v4.3h-1.1v-4c0-0.4,0-0.7-0.1-0.9
|
||||
c-0.1-0.2-0.2-0.3-0.4-0.5c-0.2-0.1-0.4-0.2-0.6-0.2c-0.4,0-0.8,0.1-1.1,0.4c-0.3,0.3-0.4,0.8-0.4,1.4v3.6h-1.1v-4.1
|
||||
c0-0.5-0.1-0.8-0.3-1.1c-0.2-0.2-0.5-0.4-0.9-0.4c-0.3,0-0.6,0.1-0.8,0.2c-0.3,0.2-0.4,0.4-0.5,0.7c-0.1,0.3-0.2,0.7-0.2,1.3v3.3
|
||||
H190.4z M199.9,253.6c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3
|
||||
c0,0.8-0.1,1.4-0.4,1.9c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8S199.9,254.6,199.9,253.6z
|
||||
M201,253.6c0,0.8,0.2,1.4,0.5,1.8s0.8,0.6,1.3,0.6c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8
|
||||
s-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6S201,252.8,201,253.6z M207,259.1v-8.7h1v0.8c0.2-0.3,0.5-0.6,0.8-0.7
|
||||
c0.3-0.2,0.6-0.2,1-0.2c0.5,0,1,0.1,1.4,0.4c0.4,0.3,0.7,0.7,0.9,1.2c0.2,0.5,0.3,1.1,0.3,1.7c0,0.6-0.1,1.2-0.3,1.7
|
||||
c-0.2,0.5-0.6,0.9-1,1.2c-0.4,0.3-0.9,0.4-1.4,0.4c-0.4,0-0.7-0.1-1-0.2c-0.3-0.2-0.5-0.3-0.7-0.6v3.1H207z M207.9,253.6
|
||||
c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.9c0-0.8-0.2-1.4-0.5-1.8
|
||||
c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6C208.1,252.2,207.9,252.8,207.9,253.6z M213.2,253.6c0-1.2,0.3-2,1-2.6
|
||||
c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9c-0.2,0.5-0.6,0.8-1.1,1.1
|
||||
c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8C213.4,255.5,213.2,254.6,213.2,253.6z M214.3,253.6c0,0.8,0.2,1.4,0.5,1.8
|
||||
c0.4,0.4,0.8,0.6,1.3,0.6c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8c-0.4-0.4-0.8-0.6-1.3-0.6
|
||||
c-0.5,0-1,0.2-1.3,0.6C214.4,252.2,214.3,252.8,214.3,253.6z M220.2,256.7v-8.7h1.1v8.7H220.2z M222.8,249.3v-1.2h1.1v1.2H222.8z
|
||||
M222.8,256.7v-6.3h1.1v6.3H222.8z M227.8,255.8l0.1,0.9c-0.3,0.1-0.6,0.1-0.8,0.1c-0.4,0-0.7-0.1-0.9-0.2
|
||||
c-0.2-0.1-0.4-0.3-0.4-0.5c-0.1-0.2-0.1-0.6-0.1-1.3v-3.6h-0.8v-0.8h0.8v-1.6l1.1-0.6v2.2h1.1v0.8h-1.1v3.7c0,0.3,0,0.5,0,0.6
|
||||
c0,0.1,0.1,0.2,0.2,0.2c0.1,0.1,0.2,0.1,0.4,0.1C227.4,255.8,227.6,255.8,227.8,255.8z M233.1,254.7l1.1,0.1
|
||||
c-0.2,0.6-0.5,1.1-1,1.5c-0.5,0.4-1.1,0.5-1.8,0.5c-0.9,0-1.6-0.3-2.2-0.8s-0.8-1.4-0.8-2.4c0-1.1,0.3-1.9,0.8-2.5
|
||||
c0.5-0.6,1.3-0.9,2.1-0.9c0.8,0,1.5,0.3,2.1,0.9s0.8,1.4,0.8,2.4c0,0.1,0,0.2,0,0.3h-4.7c0,0.7,0.2,1.2,0.6,1.6s0.8,0.5,1.3,0.5
|
||||
c0.4,0,0.7-0.1,1-0.3C232.7,255.5,232.9,255.2,233.1,254.7z M229.6,253h3.5c0-0.5-0.2-0.9-0.4-1.2c-0.3-0.4-0.8-0.6-1.3-0.6
|
||||
c-0.5,0-0.9,0.2-1.2,0.5C229.8,252,229.6,252.4,229.6,253z"/>
|
||||
<path d="M181.9,273.3c-0.6-0.7-1.1-1.6-1.5-2.6c-0.4-1-0.6-2-0.6-3.1c0-0.9,0.1-1.8,0.4-2.7c0.4-1,0.9-2,1.6-3h0.8
|
||||
c-0.5,0.8-0.8,1.4-0.9,1.8c-0.2,0.5-0.4,1.1-0.6,1.7c-0.2,0.7-0.3,1.5-0.3,2.2c0,1.9,0.6,3.8,1.8,5.7H181.9z M184,270.7v-8.7h3.3
|
||||
c0.6,0,1,0,1.3,0.1c0.4,0.1,0.8,0.2,1.1,0.4s0.5,0.5,0.7,0.8c0.2,0.4,0.3,0.8,0.3,1.2c0,0.7-0.2,1.4-0.7,1.9
|
||||
c-0.5,0.5-1.3,0.8-2.5,0.8h-2.2v3.5H184z M185.1,266.2h2.2c0.7,0,1.3-0.1,1.6-0.4c0.3-0.3,0.5-0.7,0.5-1.2c0-0.4-0.1-0.7-0.3-0.9
|
||||
c-0.2-0.3-0.4-0.4-0.7-0.5c-0.2-0.1-0.5-0.1-1.1-0.1h-2.2V266.2z M191.7,273.2l-0.1-1c0.2,0.1,0.4,0.1,0.6,0.1
|
||||
c0.2,0,0.4,0,0.6-0.1c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.4,0.3-0.8c0-0.1,0.1-0.1,0.1-0.3l-2.4-6.3h1.1l1.3,3.6
|
||||
c0.2,0.5,0.3,0.9,0.5,1.5c0.1-0.5,0.3-1,0.4-1.4l1.3-3.7h1.1l-2.4,6.4c-0.3,0.7-0.5,1.2-0.6,1.4c-0.2,0.4-0.4,0.6-0.6,0.8
|
||||
c-0.2,0.2-0.5,0.2-0.9,0.2C192.2,273.3,192,273.3,191.7,273.2z M200,269.8l0.1,0.9c-0.3,0.1-0.6,0.1-0.8,0.1
|
||||
c-0.4,0-0.7-0.1-0.9-0.2c-0.2-0.1-0.4-0.3-0.4-0.5c-0.1-0.2-0.1-0.6-0.1-1.3v-3.6h-0.8v-0.8h0.8v-1.6l1.1-0.6v2.2h1.1v0.8H199v3.7
|
||||
c0,0.3,0,0.5,0,0.6c0,0.1,0.1,0.2,0.2,0.2c0.1,0.1,0.2,0.1,0.4,0.1C199.7,269.8,199.8,269.8,200,269.8z M201,270.7v-8.7h1.1v3.1
|
||||
c0.5-0.6,1.1-0.9,1.9-0.9c0.5,0,0.9,0.1,1.2,0.3c0.3,0.2,0.6,0.4,0.7,0.8c0.2,0.3,0.2,0.8,0.2,1.4v4h-1.1v-4
|
||||
c0-0.5-0.1-0.9-0.3-1.2c-0.2-0.2-0.6-0.4-1-0.4c-0.3,0-0.6,0.1-0.9,0.2c-0.3,0.2-0.5,0.4-0.6,0.7c-0.1,0.3-0.2,0.7-0.2,1.2v3.4
|
||||
H201z M207.2,267.6c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3
|
||||
c0,0.8-0.1,1.4-0.4,1.9c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8
|
||||
C207.5,269.5,207.2,268.7,207.2,267.6z M208.3,267.6c0,0.8,0.2,1.4,0.5,1.8c0.4,0.4,0.8,0.6,1.3,0.6c0.5,0,1-0.2,1.3-0.6
|
||||
c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8c-0.4-0.4-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6
|
||||
C208.5,266.2,208.3,266.8,208.3,267.6z M214.2,270.7v-6.3h1v0.9c0.5-0.7,1.1-1,2-1c0.4,0,0.7,0.1,1,0.2c0.3,0.1,0.6,0.3,0.7,0.5
|
||||
c0.2,0.2,0.3,0.5,0.3,0.8c0,0.2,0.1,0.5,0.1,1v3.9h-1.1v-3.8c0-0.4,0-0.8-0.1-1c-0.1-0.2-0.2-0.4-0.4-0.5
|
||||
c-0.2-0.1-0.5-0.2-0.7-0.2c-0.5,0-0.8,0.1-1.2,0.4c-0.3,0.3-0.5,0.8-0.5,1.6v3.4H214.2z M221.5,273.3h-0.8
|
||||
c1.2-1.9,1.8-3.8,1.8-5.7c0-0.7-0.1-1.5-0.2-2.2c-0.1-0.6-0.3-1.2-0.6-1.7c-0.2-0.4-0.5-0.9-0.9-1.8h0.8c0.7,1,1.3,2,1.6,3
|
||||
c0.3,0.9,0.5,1.8,0.5,2.7c0,1.1-0.2,2.1-0.6,3.1C222.6,271.7,222.1,272.6,221.5,273.3z"/>
|
||||
<path fill="#FFF2CC" d="M46.4,239.6h86.8v43.4H46.4V239.6z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="round" stroke-miterlimit="10" d="M46.4,239.6h86.8v43.4
|
||||
H46.4V239.6z"/>
|
||||
<path d="M64.8,256.7V253l-3.3-5h1.4l1.7,2.6c0.3,0.5,0.6,1,0.9,1.5c0.3-0.5,0.6-1,0.9-1.5l1.7-2.5h1.3l-3.5,5v3.7H64.8z
|
||||
M69.7,253.6c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9
|
||||
c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8S69.7,254.6,69.7,253.6z M70.8,253.6
|
||||
c0,0.8,0.2,1.4,0.5,1.8c0.4,0.4,0.8,0.6,1.3,0.6c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8
|
||||
c-0.4-0.4-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6C71,252.2,70.8,252.8,70.8,253.6z M80.8,256.7v-0.9c-0.5,0.7-1.1,1.1-2,1.1
|
||||
c-0.4,0-0.7-0.1-1-0.2c-0.3-0.1-0.6-0.3-0.7-0.5c-0.2-0.2-0.3-0.5-0.3-0.8c0-0.2-0.1-0.5-0.1-1v-3.9h1.1v3.5c0,0.6,0,0.9,0.1,1.1
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.5,0.2,0.8,0.2c0.3,0,0.6-0.1,0.9-0.2c0.3-0.2,0.5-0.4,0.6-0.7c0.1-0.3,0.2-0.7,0.2-1.2v-3.4
|
||||
h1.1v6.3H80.8z M83.3,256.7v-6.3h1v1c0.2-0.4,0.5-0.7,0.7-0.9c0.2-0.1,0.4-0.2,0.7-0.2c0.4,0,0.7,0.1,1.1,0.3l-0.4,1
|
||||
c-0.3-0.2-0.5-0.2-0.8-0.2c-0.2,0-0.4,0.1-0.6,0.2c-0.2,0.1-0.3,0.3-0.4,0.6c-0.1,0.4-0.2,0.8-0.2,1.2v3.3H83.3z M96.9,253.7
|
||||
l1.1,0.3c-0.2,0.9-0.7,1.7-1.3,2.2c-0.6,0.5-1.4,0.7-2.3,0.7c-0.9,0-1.7-0.2-2.3-0.6c-0.6-0.4-1-0.9-1.3-1.7
|
||||
c-0.3-0.7-0.5-1.5-0.5-2.3c0-0.9,0.2-1.7,0.5-2.4c0.3-0.7,0.8-1.2,1.5-1.5c0.6-0.4,1.3-0.5,2.1-0.5c0.9,0,1.6,0.2,2.2,0.7
|
||||
c0.6,0.4,1,1.1,1.2,1.9l-1.1,0.3c-0.2-0.6-0.5-1.1-0.9-1.4c-0.4-0.3-0.9-0.4-1.4-0.4c-0.7,0-1.2,0.2-1.7,0.5
|
||||
c-0.4,0.3-0.8,0.8-0.9,1.3c-0.2,0.5-0.3,1.1-0.3,1.7c0,0.7,0.1,1.4,0.3,1.9c0.2,0.5,0.6,1,1,1.2c0.5,0.3,0.9,0.4,1.5,0.4
|
||||
c0.6,0,1.2-0.2,1.6-0.5C96.5,255,96.8,254.4,96.9,253.7z M98.8,253.6c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7
|
||||
c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4
|
||||
c-0.9,0-1.6-0.3-2.1-0.8C99.1,255.5,98.8,254.6,98.8,253.6z M99.9,253.6c0,0.8,0.2,1.4,0.5,1.8s0.8,0.6,1.3,0.6
|
||||
c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8s-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6S99.9,252.8,99.9,253.6z
|
||||
M109.9,256.7v-0.8c-0.4,0.6-1,0.9-1.8,0.9c-0.5,0-1-0.1-1.4-0.4c-0.4-0.3-0.7-0.7-1-1.2c-0.2-0.5-0.3-1.1-0.3-1.7
|
||||
c0-0.6,0.1-1.2,0.3-1.7c0.2-0.5,0.5-0.9,0.9-1.2c0.4-0.3,0.9-0.4,1.4-0.4c0.4,0,0.7,0.1,1,0.2c0.3,0.2,0.5,0.4,0.7,0.6v-3.1h1.1
|
||||
v8.7H109.9z M106.5,253.6c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.7
|
||||
c0-0.9-0.2-1.5-0.5-1.9c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6C106.7,252.1,106.5,252.8,106.5,253.6z M116.7,254.7
|
||||
l1.1,0.1c-0.2,0.6-0.5,1.1-1,1.5c-0.5,0.4-1.1,0.5-1.8,0.5c-0.9,0-1.6-0.3-2.2-0.8c-0.5-0.6-0.8-1.4-0.8-2.4
|
||||
c0-1.1,0.3-1.9,0.8-2.5c0.5-0.6,1.3-0.9,2.1-0.9c0.8,0,1.5,0.3,2.1,0.9c0.5,0.6,0.8,1.4,0.8,2.4c0,0.1,0,0.2,0,0.3h-4.7
|
||||
c0,0.7,0.2,1.2,0.6,1.6c0.4,0.4,0.8,0.5,1.3,0.5c0.4,0,0.7-0.1,1-0.3C116.3,255.5,116.6,255.2,116.7,254.7z M113.2,253h3.5
|
||||
c0-0.5-0.2-0.9-0.4-1.2c-0.3-0.4-0.8-0.6-1.3-0.6c-0.5,0-0.9,0.2-1.2,0.5C113.4,252,113.3,252.4,113.2,253z"/>
|
||||
<path d="M70.2,273.3c-0.6-0.7-1.1-1.6-1.5-2.6c-0.4-1-0.6-2-0.6-3.1c0-0.9,0.1-1.8,0.4-2.7c0.4-1,0.9-2,1.6-3h0.8
|
||||
c-0.5,0.8-0.8,1.4-0.9,1.8c-0.2,0.5-0.4,1.1-0.6,1.7c-0.2,0.7-0.3,1.5-0.3,2.2c0,1.9,0.6,3.8,1.8,5.7H70.2z M72.2,270.7v-8.7h3.3
|
||||
c0.6,0,1,0,1.3,0.1c0.4,0.1,0.8,0.2,1.1,0.4s0.5,0.5,0.7,0.8c0.2,0.4,0.3,0.8,0.3,1.2c0,0.7-0.2,1.4-0.7,1.9
|
||||
c-0.5,0.5-1.3,0.8-2.5,0.8h-2.2v3.5H72.2z M73.4,266.2h2.2c0.7,0,1.3-0.1,1.6-0.4c0.3-0.3,0.5-0.7,0.5-1.2c0-0.4-0.1-0.7-0.3-0.9
|
||||
c-0.2-0.3-0.4-0.4-0.7-0.5c-0.2-0.1-0.5-0.1-1.1-0.1h-2.2V266.2z M80,273.2l-0.1-1c0.2,0.1,0.4,0.1,0.6,0.1c0.2,0,0.4,0,0.6-0.1
|
||||
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.4,0.3-0.8c0-0.1,0.1-0.1,0.1-0.3l-2.4-6.3h1.1l1.3,3.6c0.2,0.5,0.3,0.9,0.5,1.5
|
||||
c0.1-0.5,0.3-1,0.4-1.4l1.3-3.7h1.1l-2.4,6.4c-0.3,0.7-0.5,1.2-0.6,1.4c-0.2,0.4-0.4,0.6-0.6,0.8c-0.2,0.2-0.5,0.2-0.9,0.2
|
||||
C80.5,273.3,80.2,273.3,80,273.2z M88.3,269.8l0.1,0.9c-0.3,0.1-0.6,0.1-0.8,0.1c-0.4,0-0.7-0.1-0.9-0.2c-0.2-0.1-0.4-0.3-0.4-0.5
|
||||
c-0.1-0.2-0.1-0.6-0.1-1.3v-3.6h-0.8v-0.8h0.8v-1.6l1.1-0.6v2.2h1.1v0.8h-1.1v3.7c0,0.3,0,0.5,0,0.6c0,0.1,0.1,0.2,0.2,0.2
|
||||
s0.2,0.1,0.4,0.1C88,269.8,88.1,269.8,88.3,269.8z M89.3,270.7v-8.7h1.1v3.1c0.5-0.6,1.1-0.9,1.9-0.9c0.5,0,0.9,0.1,1.2,0.3
|
||||
c0.3,0.2,0.6,0.4,0.7,0.8c0.2,0.3,0.2,0.8,0.2,1.4v4h-1.1v-4c0-0.5-0.1-0.9-0.3-1.2c-0.2-0.2-0.6-0.4-1-0.4
|
||||
c-0.3,0-0.6,0.1-0.9,0.2c-0.3,0.2-0.5,0.4-0.6,0.7c-0.1,0.3-0.2,0.7-0.2,1.2v3.4H89.3z M95.5,267.6c0-1.2,0.3-2,1-2.6
|
||||
c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9c-0.2,0.5-0.6,0.8-1.1,1.1
|
||||
c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8C95.8,269.5,95.5,268.7,95.5,267.6z M96.6,267.6c0,0.8,0.2,1.4,0.5,1.8
|
||||
c0.4,0.4,0.8,0.6,1.3,0.6c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8c-0.4-0.4-0.8-0.6-1.3-0.6
|
||||
c-0.5,0-1,0.2-1.3,0.6C96.8,266.2,96.6,266.8,96.6,267.6z M102.5,270.7v-6.3h1v0.9c0.5-0.7,1.1-1,2-1c0.4,0,0.7,0.1,1,0.2
|
||||
c0.3,0.1,0.6,0.3,0.7,0.5c0.2,0.2,0.3,0.5,0.3,0.8c0,0.2,0.1,0.5,0.1,1v3.9h-1.1v-3.8c0-0.4,0-0.8-0.1-1c-0.1-0.2-0.2-0.4-0.4-0.5
|
||||
c-0.2-0.1-0.5-0.2-0.7-0.2c-0.5,0-0.8,0.1-1.2,0.4c-0.3,0.3-0.5,0.8-0.5,1.6v3.4H102.5z M109.8,273.3h-0.8
|
||||
c1.2-1.9,1.8-3.8,1.8-5.7c0-0.7-0.1-1.5-0.2-2.2c-0.1-0.6-0.3-1.2-0.6-1.7c-0.2-0.4-0.5-0.9-0.9-1.8h0.8c0.7,1,1.3,2,1.6,3
|
||||
c0.3,0.9,0.5,1.8,0.5,2.7c0,1.1-0.2,2.1-0.6,3.1C110.9,271.7,110.4,272.6,109.8,273.3z"/>
|
||||
<path fill="#E6B8AF" d="M280.2,239.1L280.2,239.1c0,3,9.7,5.4,21.7,5.4s21.7-2.4,21.7-5.4v44.3c0,3-9.7,5.4-21.7,5.4
|
||||
s-21.7-2.4-21.7-5.4V239.1z"/>
|
||||
<path fill="#F0D4CF" d="M280.2,239.1L280.2,239.1c0-3,9.7-5.4,21.7-5.4s21.7,2.4,21.7,5.4l0,0c0,3-9.7,5.4-21.7,5.4
|
||||
S280.2,242.1,280.2,239.1z"/>
|
||||
<path fill-opacity="0" d="M323.5,239.1L323.5,239.1c0,3-9.7,5.4-21.7,5.4s-21.7-2.4-21.7-5.4l0,0c0-3,9.7-5.4,21.7-5.4
|
||||
S323.5,236.1,323.5,239.1v44.3c0,3-9.7,5.4-21.7,5.4s-21.7-2.4-21.7-5.4v-44.3"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="round" stroke-miterlimit="10" d="M323.5,239.1L323.5,239.1
|
||||
c0,3-9.7,5.4-21.7,5.4s-21.7-2.4-21.7-5.4l0,0c0-3,9.7-5.4,21.7-5.4S323.5,236.1,323.5,239.1v44.3c0,3-9.7,5.4-21.7,5.4
|
||||
s-21.7-2.4-21.7-5.4v-44.3"/>
|
||||
<path d="M290.2,261.3v-8.7h3c0.7,0,1.2,0,1.5,0.1c0.5,0.1,0.9,0.3,1.3,0.6c0.5,0.4,0.8,0.9,1,1.5c0.2,0.6,0.3,1.3,0.3,2.1
|
||||
c0,0.7-0.1,1.2-0.2,1.8c-0.2,0.5-0.3,0.9-0.6,1.3c-0.2,0.3-0.5,0.6-0.8,0.8c-0.3,0.2-0.6,0.3-1,0.4c-0.4,0.1-0.9,0.1-1.4,0.1
|
||||
H290.2z M291.4,260.3h1.9c0.6,0,1-0.1,1.3-0.2c0.3-0.1,0.6-0.3,0.8-0.4c0.3-0.3,0.5-0.6,0.6-1.1c0.2-0.5,0.2-1,0.2-1.7
|
||||
c0-0.9-0.1-1.6-0.4-2.1c-0.3-0.5-0.7-0.8-1.1-1c-0.3-0.1-0.8-0.2-1.5-0.2h-1.8V260.3z M302.8,260.6c-0.4,0.3-0.8,0.6-1.1,0.7
|
||||
c-0.4,0.1-0.8,0.2-1.2,0.2c-0.7,0-1.2-0.2-1.6-0.5c-0.4-0.3-0.6-0.8-0.6-1.3c0-0.3,0.1-0.6,0.2-0.8c0.1-0.3,0.3-0.5,0.5-0.6
|
||||
c0.2-0.2,0.5-0.3,0.8-0.3c0.2-0.1,0.5-0.1,0.9-0.2c0.9-0.1,1.5-0.2,1.9-0.4c0-0.1,0-0.2,0-0.3c0-0.4-0.1-0.7-0.3-0.9
|
||||
c-0.3-0.2-0.7-0.4-1.2-0.4c-0.5,0-0.9,0.1-1.1,0.3c-0.2,0.2-0.4,0.5-0.5,0.9l-1-0.1c0.1-0.4,0.3-0.8,0.5-1.1
|
||||
c0.2-0.3,0.5-0.5,0.9-0.6c0.4-0.2,0.9-0.2,1.4-0.2c0.5,0,1,0.1,1.3,0.2c0.3,0.1,0.6,0.3,0.7,0.5s0.3,0.4,0.3,0.7
|
||||
c0,0.2,0.1,0.5,0.1,1v1.4c0,1,0,1.6,0.1,1.9c0,0.3,0.1,0.5,0.3,0.7H303C302.9,261.1,302.8,260.9,302.8,260.6z M302.7,258.2
|
||||
c-0.4,0.2-1,0.3-1.7,0.4c-0.4,0.1-0.7,0.1-0.9,0.2s-0.3,0.2-0.4,0.3s-0.1,0.3-0.1,0.5c0,0.3,0.1,0.5,0.3,0.7
|
||||
c0.2,0.2,0.5,0.3,0.9,0.3c0.4,0,0.8-0.1,1.1-0.3c0.3-0.2,0.6-0.4,0.7-0.7c0.1-0.2,0.2-0.6,0.2-1.1V258.2z M307.6,260.4l0.1,0.9
|
||||
c-0.3,0.1-0.6,0.1-0.8,0.1c-0.4,0-0.7-0.1-0.9-0.2c-0.2-0.1-0.4-0.3-0.4-0.5c-0.1-0.2-0.1-0.6-0.1-1.3v-3.6h-0.8v-0.8h0.8v-1.6
|
||||
l1.1-0.6v2.2h1.1v0.8h-1.1v3.7c0,0.3,0,0.5,0,0.6c0,0.1,0.1,0.2,0.2,0.2c0.1,0.1,0.2,0.1,0.4,0.1
|
||||
C307.3,260.4,307.4,260.4,307.6,260.4z M312.7,260.6c-0.4,0.3-0.8,0.6-1.1,0.7c-0.4,0.1-0.8,0.2-1.2,0.2c-0.7,0-1.2-0.2-1.6-0.5
|
||||
c-0.4-0.3-0.6-0.8-0.6-1.3c0-0.3,0.1-0.6,0.2-0.8s0.3-0.5,0.5-0.6c0.2-0.2,0.5-0.3,0.8-0.3c0.2-0.1,0.5-0.1,0.9-0.2
|
||||
c0.9-0.1,1.5-0.2,1.9-0.4c0-0.1,0-0.2,0-0.3c0-0.4-0.1-0.7-0.3-0.9c-0.3-0.2-0.7-0.4-1.2-0.4c-0.5,0-0.9,0.1-1.1,0.3
|
||||
c-0.2,0.2-0.4,0.5-0.5,0.9l-1-0.1c0.1-0.4,0.3-0.8,0.5-1.1c0.2-0.3,0.5-0.5,0.9-0.6c0.4-0.2,0.9-0.2,1.4-0.2c0.5,0,1,0.1,1.3,0.2
|
||||
c0.3,0.1,0.6,0.3,0.7,0.5c0.2,0.2,0.3,0.4,0.3,0.7c0,0.2,0.1,0.5,0.1,1v1.4c0,1,0,1.6,0.1,1.9c0,0.3,0.1,0.5,0.3,0.7h-1.1
|
||||
C312.8,261.1,312.7,260.9,312.7,260.6z M312.6,258.2c-0.4,0.2-1,0.3-1.7,0.4c-0.4,0.1-0.7,0.1-0.9,0.2s-0.3,0.2-0.4,0.3
|
||||
s-0.1,0.3-0.1,0.5c0,0.3,0.1,0.5,0.3,0.7c0.2,0.2,0.5,0.3,0.9,0.3s0.8-0.1,1.1-0.3c0.3-0.2,0.6-0.4,0.7-0.7
|
||||
c0.1-0.2,0.2-0.6,0.2-1.1V258.2z"/>
|
||||
<path d="M289,273.5l1.1-0.2c0.1,0.4,0.2,0.8,0.5,1c0.3,0.2,0.7,0.3,1.1,0.3c0.5,0,0.9-0.1,1.1-0.3c0.2-0.2,0.4-0.4,0.4-0.7
|
||||
c0-0.2-0.1-0.4-0.3-0.6c-0.1-0.1-0.5-0.2-1.1-0.4c-0.8-0.2-1.3-0.4-1.6-0.5c-0.3-0.1-0.5-0.3-0.7-0.6c-0.2-0.3-0.2-0.5-0.2-0.8
|
||||
c0-0.3,0.1-0.5,0.2-0.8c0.1-0.2,0.3-0.4,0.5-0.6c0.2-0.1,0.4-0.2,0.7-0.3c0.3-0.1,0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.3,0.2
|
||||
c0.4,0.1,0.6,0.3,0.8,0.6c0.2,0.2,0.3,0.6,0.4,1l-1,0.1c0-0.3-0.2-0.6-0.4-0.7c-0.2-0.2-0.5-0.3-1-0.3c-0.5,0-0.8,0.1-1,0.2
|
||||
c-0.2,0.2-0.3,0.3-0.3,0.6c0,0.1,0,0.3,0.1,0.4c0.1,0.1,0.2,0.2,0.4,0.3c0.1,0,0.4,0.1,0.9,0.3c0.8,0.2,1.3,0.4,1.6,0.5
|
||||
s0.5,0.3,0.7,0.6c0.2,0.2,0.3,0.6,0.3,0.9c0,0.4-0.1,0.7-0.3,1c-0.2,0.3-0.5,0.6-0.9,0.7c-0.4,0.2-0.8,0.2-1.3,0.2
|
||||
c-0.8,0-1.4-0.2-1.9-0.5C289.4,274.7,289.1,274.2,289,273.5z M297.7,274.4l0.1,0.9c-0.3,0.1-0.6,0.1-0.8,0.1
|
||||
c-0.4,0-0.7-0.1-0.9-0.2c-0.2-0.1-0.4-0.3-0.4-0.5c-0.1-0.2-0.1-0.6-0.1-1.3v-3.6h-0.8v-0.8h0.8v-1.6l1.1-0.6v2.2h1.1v0.8h-1.1
|
||||
v3.7c0,0.3,0,0.5,0,0.6c0,0.1,0.1,0.2,0.2,0.2c0.1,0.1,0.2,0.1,0.4,0.1C297.3,274.5,297.5,274.4,297.7,274.4z M298.3,272.2
|
||||
c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9
|
||||
c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8C298.5,274.1,298.3,273.3,298.3,272.2z M299.4,272.2
|
||||
c0,0.8,0.2,1.4,0.5,1.8c0.4,0.4,0.8,0.6,1.3,0.6s1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8
|
||||
c-0.4-0.4-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6C299.5,270.8,299.4,271.4,299.4,272.2z M305.3,275.4v-6.3h1v1
|
||||
c0.2-0.4,0.5-0.7,0.7-0.9c0.2-0.1,0.4-0.2,0.7-0.2c0.4,0,0.7,0.1,1.1,0.3l-0.4,1c-0.3-0.2-0.5-0.2-0.8-0.2c-0.2,0-0.4,0.1-0.6,0.2
|
||||
c-0.2,0.1-0.3,0.3-0.4,0.6c-0.1,0.4-0.2,0.8-0.2,1.2v3.3H305.3z M313.5,273.3l1.1,0.1c-0.2,0.6-0.5,1.1-1,1.5
|
||||
c-0.5,0.4-1.1,0.5-1.8,0.5c-0.9,0-1.6-0.3-2.2-0.8c-0.5-0.6-0.8-1.4-0.8-2.4c0-1.1,0.3-1.9,0.8-2.5c0.5-0.6,1.3-0.9,2.1-0.9
|
||||
c0.8,0,1.5,0.3,2.1,0.9c0.5,0.6,0.8,1.4,0.8,2.4c0,0.1,0,0.2,0,0.3H310c0,0.7,0.2,1.2,0.6,1.6c0.4,0.4,0.8,0.5,1.3,0.5
|
||||
c0.4,0,0.7-0.1,1-0.3C313.2,274.1,313.4,273.8,313.5,273.3z M310.1,271.6h3.5c0-0.5-0.2-0.9-0.4-1.2c-0.3-0.4-0.8-0.6-1.3-0.6
|
||||
c-0.5,0-0.9,0.2-1.2,0.5C310.3,270.6,310.1,271.1,310.1,271.6z"/>
|
||||
<path fill-opacity="0" d="M244.9,261.2h35.2"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="round" stroke-miterlimit="10" d="M252.6,261.2h19.9"/>
|
||||
<path stroke="#000000" stroke-width="2" stroke-miterlimit="10" d="M252.6,259.1l-5.8,2.1l5.8,2.1V259.1z"/>
|
||||
<path stroke="#000000" stroke-width="2" stroke-miterlimit="10" d="M272.5,263.3l5.8-2.1l-5.8-2.1V263.3z"/>
|
||||
<path fill="#CFE2F3" d="M48,334.3h257.4v75.9H48L48,334.3z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="round" stroke-miterlimit="10" d="M48,334.3h257.4v75.9H48
|
||||
L48,334.3z"/>
|
||||
<path d="M131.2,348.5l1.1,0.3c-0.2,0.9-0.7,1.7-1.3,2.2c-0.6,0.5-1.4,0.7-2.3,0.7c-0.9,0-1.7-0.2-2.3-0.6c-0.6-0.4-1-0.9-1.3-1.7
|
||||
c-0.3-0.7-0.5-1.5-0.5-2.3c0-0.9,0.2-1.7,0.5-2.4c0.3-0.7,0.8-1.2,1.5-1.5c0.6-0.4,1.3-0.5,2.1-0.5c0.9,0,1.6,0.2,2.2,0.7
|
||||
c0.6,0.4,1,1.1,1.2,1.9l-1.1,0.3c-0.2-0.6-0.5-1.1-0.9-1.4c-0.4-0.3-0.9-0.4-1.4-0.4c-0.7,0-1.2,0.2-1.7,0.5
|
||||
c-0.4,0.3-0.8,0.8-0.9,1.3c-0.2,0.5-0.3,1.1-0.3,1.7c0,0.7,0.1,1.4,0.3,1.9c0.2,0.5,0.6,1,1,1.2c0.5,0.3,0.9,0.4,1.5,0.4
|
||||
c0.6,0,1.2-0.2,1.6-0.5C130.8,349.8,131.1,349.2,131.2,348.5z M133.5,351.5v-8.7h1.1v8.7H133.5z M136.2,344.1v-1.2h1.1v1.2H136.2z
|
||||
M136.2,351.5v-6.3h1.1v6.3H136.2z M143.1,349.5l1.1,0.1c-0.2,0.6-0.5,1.1-1,1.5c-0.5,0.4-1.1,0.5-1.8,0.5c-0.9,0-1.6-0.3-2.2-0.8
|
||||
c-0.5-0.6-0.8-1.4-0.8-2.4c0-1.1,0.3-1.9,0.8-2.5c0.5-0.6,1.3-0.9,2.1-0.9c0.8,0,1.5,0.3,2.1,0.9c0.5,0.6,0.8,1.4,0.8,2.4
|
||||
c0,0.1,0,0.2,0,0.3h-4.7c0,0.7,0.2,1.2,0.6,1.6c0.4,0.4,0.8,0.5,1.3,0.5c0.4,0,0.7-0.1,1-0.3C142.7,350.3,142.9,349.9,143.1,349.5
|
||||
z M139.6,347.8h3.5c0-0.5-0.2-0.9-0.4-1.2c-0.3-0.4-0.8-0.6-1.3-0.6c-0.5,0-0.9,0.2-1.2,0.5C139.8,346.8,139.6,347.2,139.6,347.8z
|
||||
M145.4,351.5v-6.3h1v0.9c0.5-0.7,1.1-1,2-1c0.4,0,0.7,0.1,1,0.2c0.3,0.1,0.6,0.3,0.7,0.5c0.2,0.2,0.3,0.5,0.3,0.8
|
||||
c0,0.2,0.1,0.5,0.1,1v3.9h-1.1v-3.8c0-0.4,0-0.8-0.1-1c-0.1-0.2-0.2-0.4-0.4-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.5,0-0.8,0.1-1.2,0.4
|
||||
c-0.3,0.3-0.5,0.8-0.5,1.6v3.4H145.4z M154.4,350.6l0.1,0.9c-0.3,0.1-0.6,0.1-0.8,0.1c-0.4,0-0.7-0.1-0.9-0.2
|
||||
c-0.2-0.1-0.4-0.3-0.4-0.5c-0.1-0.2-0.1-0.6-0.1-1.3v-3.6h-0.8v-0.8h0.8v-1.6l1.1-0.6v2.2h1.1v0.8h-1.1v3.7c0,0.3,0,0.5,0,0.6
|
||||
c0,0.1,0.1,0.2,0.2,0.2c0.1,0.1,0.2,0.1,0.4,0.1C154,350.6,154.2,350.6,154.4,350.6z M160.3,351.5l-2.3-8.7h1.2l1.3,5.7
|
||||
c0.1,0.6,0.3,1.2,0.4,1.8c0.2-0.9,0.3-1.5,0.4-1.6l1.7-5.8h1.4l1.2,4.4c0.3,1.1,0.5,2.1,0.7,3.1c0.1-0.5,0.3-1.2,0.4-1.9l1.4-5.6
|
||||
h1.2l-2.4,8.7h-1.1l-1.8-6.6c-0.2-0.6-0.2-0.9-0.3-1c-0.1,0.4-0.2,0.7-0.3,1l-1.8,6.6H160.3z M174.2,349.5l1.1,0.1
|
||||
c-0.2,0.6-0.5,1.1-1,1.5c-0.5,0.4-1.1,0.5-1.8,0.5c-0.9,0-1.6-0.3-2.2-0.8c-0.5-0.6-0.8-1.4-0.8-2.4c0-1.1,0.3-1.9,0.8-2.5
|
||||
c0.5-0.6,1.3-0.9,2.1-0.9c0.8,0,1.5,0.3,2.1,0.9c0.5,0.6,0.8,1.4,0.8,2.4c0,0.1,0,0.2,0,0.3h-4.7c0,0.7,0.2,1.2,0.6,1.6
|
||||
c0.4,0.4,0.8,0.5,1.3,0.5c0.4,0,0.7-0.1,1-0.3C173.8,350.3,174,349.9,174.2,349.5z M170.7,347.8h3.5c0-0.5-0.2-0.9-0.4-1.2
|
||||
c-0.3-0.4-0.8-0.6-1.3-0.6c-0.5,0-0.9,0.2-1.2,0.5C170.9,346.8,170.7,347.2,170.7,347.8z M177.5,351.5h-1v-8.7h1.1v3.1
|
||||
c0.5-0.6,1-0.8,1.7-0.8c0.4,0,0.8,0.1,1.1,0.2c0.3,0.2,0.6,0.4,0.8,0.7c0.2,0.3,0.4,0.6,0.5,1c0.1,0.4,0.2,0.8,0.2,1.3
|
||||
c0,1.1-0.3,1.9-0.8,2.5c-0.5,0.6-1.2,0.9-1.9,0.9s-1.3-0.3-1.7-0.9V351.5z M177.4,348.3c0,0.8,0.1,1.3,0.3,1.6
|
||||
c0.3,0.5,0.8,0.8,1.4,0.8c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8c-0.3-0.4-0.7-0.6-1.2-0.6
|
||||
c-0.5,0-0.9,0.2-1.2,0.6C177.6,347,177.4,347.6,177.4,348.3z M186.5,351.5v-8.7h3.3c0.7,0,1.2,0.1,1.6,0.3
|
||||
c0.4,0.2,0.7,0.4,0.9,0.8c0.2,0.4,0.3,0.7,0.3,1.1c0,0.4-0.1,0.7-0.3,1.1c-0.2,0.3-0.5,0.6-0.9,0.8c0.5,0.2,0.9,0.4,1.2,0.8
|
||||
c0.3,0.4,0.4,0.8,0.4,1.3c0,0.4-0.1,0.8-0.3,1.1c-0.2,0.3-0.4,0.6-0.6,0.8c-0.3,0.2-0.6,0.3-1,0.4c-0.4,0.1-0.8,0.1-1.4,0.1H186.5
|
||||
z M187.6,346.5h1.9c0.5,0,0.9,0,1.1-0.1c0.3-0.1,0.5-0.2,0.7-0.4c0.2-0.2,0.2-0.5,0.2-0.8c0-0.3-0.1-0.5-0.2-0.8
|
||||
c-0.1-0.2-0.3-0.4-0.6-0.4c-0.3-0.1-0.7-0.1-1.3-0.1h-1.7V346.5z M187.6,350.5h2.2c0.4,0,0.6,0,0.8,0c0.3,0,0.5-0.1,0.7-0.2
|
||||
c0.2-0.1,0.3-0.3,0.4-0.5c0.1-0.2,0.2-0.5,0.2-0.7c0-0.3-0.1-0.6-0.2-0.8s-0.4-0.4-0.7-0.5c-0.3-0.1-0.7-0.1-1.3-0.1h-2V350.5z
|
||||
M194.3,351.5v-6.3h1v1c0.2-0.4,0.5-0.7,0.7-0.9c0.2-0.1,0.4-0.2,0.7-0.2c0.4,0,0.7,0.1,1.1,0.3l-0.4,1c-0.3-0.2-0.5-0.2-0.8-0.2
|
||||
c-0.2,0-0.4,0.1-0.6,0.2c-0.2,0.1-0.3,0.3-0.4,0.6c-0.1,0.4-0.2,0.8-0.2,1.2v3.3H194.3z M197.9,348.4c0-1.2,0.3-2,1-2.6
|
||||
c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9c-0.2,0.5-0.6,0.8-1.1,1.1
|
||||
c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8C198.2,350.2,197.9,349.4,197.9,348.4z M199,348.4c0,0.8,0.2,1.4,0.5,1.8
|
||||
s0.8,0.6,1.3,0.6c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8s-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6
|
||||
S199,347.6,199,348.4z M206.1,351.5l-1.9-6.3h1.1l1,3.6l0.4,1.4c0-0.1,0.1-0.5,0.3-1.3l1-3.7h1.1l0.9,3.6l0.3,1.2l0.4-1.2l1.1-3.6
|
||||
h1l-2,6.3h-1.1l-1-3.8l-0.2-1.1l-1.3,4.8H206.1z M213.1,349.6l1.1-0.2c0.1,0.4,0.2,0.8,0.5,1c0.3,0.2,0.7,0.3,1.1,0.3
|
||||
c0.5,0,0.9-0.1,1.1-0.3c0.2-0.2,0.4-0.4,0.4-0.7c0-0.2-0.1-0.4-0.3-0.6c-0.1-0.1-0.5-0.2-1.1-0.4c-0.8-0.2-1.3-0.4-1.6-0.5
|
||||
c-0.3-0.1-0.5-0.3-0.7-0.6c-0.2-0.3-0.2-0.5-0.2-0.8c0-0.3,0.1-0.5,0.2-0.8c0.1-0.2,0.3-0.4,0.5-0.6c0.2-0.1,0.4-0.2,0.7-0.3
|
||||
c0.3-0.1,0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.3,0.2c0.4,0.1,0.6,0.3,0.8,0.6c0.2,0.2,0.3,0.6,0.4,1l-1,0.1c0-0.3-0.2-0.6-0.4-0.7
|
||||
c-0.2-0.2-0.5-0.3-1-0.3c-0.5,0-0.8,0.1-1,0.2c-0.2,0.2-0.3,0.3-0.3,0.6c0,0.1,0,0.3,0.1,0.4c0.1,0.1,0.2,0.2,0.4,0.3
|
||||
c0.1,0,0.4,0.1,0.9,0.3c0.8,0.2,1.3,0.4,1.6,0.5c0.3,0.1,0.5,0.3,0.7,0.6c0.2,0.2,0.3,0.6,0.3,0.9c0,0.4-0.1,0.7-0.3,1
|
||||
c-0.2,0.3-0.5,0.6-0.9,0.7c-0.4,0.2-0.8,0.2-1.3,0.2c-0.8,0-1.4-0.2-1.9-0.5C213.4,350.8,213.2,350.3,213.1,349.6z M223.7,349.5
|
||||
l1.1,0.1c-0.2,0.6-0.5,1.1-1,1.5c-0.5,0.4-1.1,0.5-1.8,0.5c-0.9,0-1.6-0.3-2.2-0.8c-0.5-0.6-0.8-1.4-0.8-2.4
|
||||
c0-1.1,0.3-1.9,0.8-2.5c0.5-0.6,1.3-0.9,2.1-0.9c0.8,0,1.5,0.3,2.1,0.9c0.5,0.6,0.8,1.4,0.8,2.4c0,0.1,0,0.2,0,0.3h-4.7
|
||||
c0,0.7,0.2,1.2,0.6,1.6c0.4,0.4,0.8,0.5,1.3,0.5c0.4,0,0.7-0.1,1-0.3C223.3,350.3,223.6,349.9,223.7,349.5z M220.2,347.8h3.5
|
||||
c0-0.5-0.2-0.9-0.4-1.2c-0.3-0.4-0.8-0.6-1.3-0.6c-0.5,0-0.9,0.2-1.2,0.5C220.5,346.8,220.3,347.2,220.2,347.8z M226,351.5v-6.3h1
|
||||
v1c0.2-0.4,0.5-0.7,0.7-0.9c0.2-0.1,0.4-0.2,0.7-0.2c0.4,0,0.7,0.1,1.1,0.3l-0.4,1c-0.3-0.2-0.5-0.2-0.8-0.2
|
||||
c-0.2,0-0.4,0.1-0.6,0.2c-0.2,0.1-0.3,0.3-0.4,0.6c-0.1,0.4-0.2,0.8-0.2,1.2v3.3H226z"/>
|
||||
<path fill="#FFF2CC" d="M59.1,358.9h86.8v43.4H59.1L59.1,358.9z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="round" stroke-miterlimit="10" d="M59.1,358.9h86.8v43.4
|
||||
H59.1L59.1,358.9z"/>
|
||||
<path d="M77.4,376v-3.7l-3.3-5h1.4l1.7,2.6c0.3,0.5,0.6,1,0.9,1.5c0.3-0.5,0.6-1,0.9-1.5l1.7-2.5H82l-3.5,5v3.7H77.4z M82.4,372.9
|
||||
c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9
|
||||
c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8C82.6,374.7,82.4,373.9,82.4,372.9z M83.4,372.9
|
||||
c0,0.8,0.2,1.4,0.5,1.8c0.4,0.4,0.8,0.6,1.3,0.6c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8
|
||||
c-0.4-0.4-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6C83.6,371.5,83.4,372.1,83.4,372.9z M93.5,376v-0.9c-0.5,0.7-1.1,1.1-2,1.1
|
||||
c-0.4,0-0.7-0.1-1-0.2c-0.3-0.1-0.6-0.3-0.7-0.5c-0.2-0.2-0.3-0.5-0.3-0.8c0-0.2-0.1-0.5-0.1-1v-3.9h1.1v3.5c0,0.6,0,0.9,0.1,1.1
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.5,0.2,0.8,0.2s0.6-0.1,0.9-0.2c0.3-0.2,0.5-0.4,0.6-0.7c0.1-0.3,0.2-0.7,0.2-1.2v-3.4h1.1v6.3
|
||||
H93.5z M96,376v-6.3h1v1c0.2-0.4,0.5-0.7,0.7-0.9c0.2-0.1,0.4-0.2,0.7-0.2c0.4,0,0.7,0.1,1.1,0.3l-0.4,1c-0.3-0.2-0.5-0.2-0.8-0.2
|
||||
c-0.2,0-0.4,0.1-0.6,0.2c-0.2,0.1-0.3,0.3-0.4,0.6c-0.1,0.4-0.2,0.8-0.2,1.2v3.3H96z M109.6,373l1.1,0.3c-0.2,0.9-0.7,1.7-1.3,2.2
|
||||
c-0.6,0.5-1.4,0.7-2.3,0.7c-0.9,0-1.7-0.2-2.3-0.6c-0.6-0.4-1-0.9-1.3-1.7c-0.3-0.7-0.5-1.5-0.5-2.3c0-0.9,0.2-1.7,0.5-2.4
|
||||
c0.3-0.7,0.8-1.2,1.5-1.5c0.6-0.4,1.3-0.5,2.1-0.5c0.9,0,1.6,0.2,2.2,0.7c0.6,0.4,1,1.1,1.2,1.9l-1.1,0.3
|
||||
c-0.2-0.6-0.5-1.1-0.9-1.4c-0.4-0.3-0.9-0.4-1.4-0.4c-0.7,0-1.2,0.2-1.7,0.5c-0.4,0.3-0.8,0.8-0.9,1.3c-0.2,0.5-0.3,1.1-0.3,1.7
|
||||
c0,0.7,0.1,1.4,0.3,1.9c0.2,0.5,0.6,1,1,1.2c0.5,0.3,0.9,0.4,1.5,0.4c0.6,0,1.2-0.2,1.6-0.5C109.1,374.3,109.4,373.7,109.6,373z
|
||||
M111.4,372.9c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9
|
||||
c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8C111.7,374.7,111.4,373.9,111.4,372.9z M112.5,372.9
|
||||
c0,0.8,0.2,1.4,0.5,1.8s0.8,0.6,1.3,0.6c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8s-0.8-0.6-1.3-0.6
|
||||
c-0.5,0-1,0.2-1.3,0.6S112.5,372.1,112.5,372.9z M122.5,376v-0.8c-0.4,0.6-1,0.9-1.8,0.9c-0.5,0-1-0.1-1.4-0.4
|
||||
c-0.4-0.3-0.7-0.7-1-1.2c-0.2-0.5-0.3-1.1-0.3-1.7c0-0.6,0.1-1.2,0.3-1.7c0.2-0.5,0.5-0.9,0.9-1.2c0.4-0.3,0.9-0.4,1.4-0.4
|
||||
c0.4,0,0.7,0.1,1,0.2c0.3,0.2,0.5,0.4,0.7,0.6v-3.1h1.1v8.7H122.5z M119.2,372.9c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6
|
||||
c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.7c0-0.9-0.2-1.5-0.5-1.9c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6
|
||||
C119.3,371.4,119.2,372,119.2,372.9z M129.4,374l1.1,0.1c-0.2,0.6-0.5,1.1-1,1.5c-0.5,0.4-1.1,0.5-1.8,0.5c-0.9,0-1.6-0.3-2.2-0.8
|
||||
c-0.5-0.6-0.8-1.4-0.8-2.4c0-1.1,0.3-1.9,0.8-2.5c0.5-0.6,1.3-0.9,2.1-0.9c0.8,0,1.5,0.3,2.1,0.9c0.5,0.6,0.8,1.4,0.8,2.4
|
||||
c0,0.1,0,0.2,0,0.3h-4.7c0,0.7,0.2,1.2,0.6,1.6c0.4,0.4,0.8,0.5,1.3,0.5c0.4,0,0.7-0.1,1-0.3C129,374.8,129.2,374.4,129.4,374z
|
||||
M125.9,372.3h3.5c0-0.5-0.2-0.9-0.4-1.2c-0.3-0.4-0.8-0.6-1.3-0.6c-0.5,0-0.9,0.2-1.2,0.5C126.1,371.3,125.9,371.7,125.9,372.3z"
|
||||
/>
|
||||
<path d="M73.6,392.6c-0.6-0.7-1.1-1.6-1.5-2.6c-0.4-1-0.6-2-0.6-3.1c0-0.9,0.1-1.8,0.4-2.7c0.4-1,0.9-2,1.6-3h0.8
|
||||
c-0.5,0.8-0.8,1.4-0.9,1.8c-0.2,0.5-0.4,1.1-0.6,1.7c-0.2,0.7-0.3,1.5-0.3,2.2c0,1.9,0.6,3.8,1.8,5.7H73.6z M75,387.6l1-0.1
|
||||
c0,0.7,0.1,1.1,0.4,1.4c0.2,0.2,0.5,0.4,0.9,0.4c0.3,0,0.5-0.1,0.7-0.2c0.2-0.1,0.4-0.3,0.4-0.5c0.1-0.2,0.1-0.6,0.1-1.1v-6h1.2
|
||||
v5.9c0,0.7-0.1,1.3-0.3,1.7c-0.2,0.4-0.5,0.7-0.8,0.9c-0.4,0.2-0.8,0.3-1.3,0.3c-0.8,0-1.3-0.2-1.8-0.7
|
||||
C75.2,389.1,75,388.4,75,387.6z M85.5,389.3c-0.4,0.3-0.8,0.6-1.1,0.7c-0.4,0.1-0.8,0.2-1.2,0.2c-0.7,0-1.2-0.2-1.6-0.5
|
||||
c-0.4-0.3-0.6-0.8-0.6-1.3c0-0.3,0.1-0.6,0.2-0.8c0.1-0.3,0.3-0.5,0.5-0.6c0.2-0.2,0.5-0.3,0.8-0.3c0.2-0.1,0.5-0.1,0.9-0.2
|
||||
c0.9-0.1,1.5-0.2,1.9-0.4c0-0.1,0-0.2,0-0.3c0-0.4-0.1-0.7-0.3-0.9c-0.3-0.2-0.7-0.4-1.2-0.4c-0.5,0-0.9,0.1-1.1,0.3
|
||||
c-0.2,0.2-0.4,0.5-0.5,0.9l-1-0.1c0.1-0.4,0.3-0.8,0.5-1.1c0.2-0.3,0.5-0.5,0.9-0.6c0.4-0.2,0.9-0.2,1.4-0.2c0.5,0,1,0.1,1.3,0.2
|
||||
c0.3,0.1,0.6,0.3,0.7,0.5c0.2,0.2,0.3,0.4,0.3,0.7c0,0.2,0.1,0.5,0.1,1v1.4c0,1,0,1.6,0.1,1.9c0,0.3,0.1,0.5,0.3,0.7h-1.1
|
||||
C85.6,389.8,85.6,389.6,85.5,389.3z M85.4,386.9c-0.4,0.2-1,0.3-1.7,0.4c-0.4,0.1-0.7,0.1-0.9,0.2c-0.2,0.1-0.3,0.2-0.4,0.3
|
||||
c-0.1,0.2-0.1,0.3-0.1,0.5c0,0.3,0.1,0.5,0.3,0.7c0.2,0.2,0.5,0.3,0.9,0.3s0.8-0.1,1.1-0.3c0.3-0.2,0.6-0.4,0.7-0.7
|
||||
c0.1-0.2,0.2-0.6,0.2-1.1V386.9z M89.8,390l-2.4-6.3h1.1l1.4,3.8c0.1,0.4,0.3,0.8,0.4,1.3c0.1-0.3,0.2-0.7,0.4-1.2l1.4-3.8h1.1
|
||||
l-2.4,6.3H89.8z M98.1,389.3c-0.4,0.3-0.8,0.6-1.1,0.7c-0.4,0.1-0.8,0.2-1.2,0.2c-0.7,0-1.2-0.2-1.6-0.5c-0.4-0.3-0.6-0.8-0.6-1.3
|
||||
c0-0.3,0.1-0.6,0.2-0.8c0.1-0.3,0.3-0.5,0.5-0.6c0.2-0.2,0.5-0.3,0.8-0.3c0.2-0.1,0.5-0.1,0.9-0.2c0.9-0.1,1.5-0.2,1.9-0.4
|
||||
c0-0.1,0-0.2,0-0.3c0-0.4-0.1-0.7-0.3-0.9c-0.3-0.2-0.7-0.4-1.2-0.4c-0.5,0-0.9,0.1-1.1,0.3c-0.2,0.2-0.4,0.5-0.5,0.9l-1-0.1
|
||||
c0.1-0.4,0.3-0.8,0.5-1.1c0.2-0.3,0.5-0.5,0.9-0.6c0.4-0.2,0.9-0.2,1.4-0.2c0.5,0,1,0.1,1.3,0.2c0.3,0.1,0.6,0.3,0.7,0.5
|
||||
c0.2,0.2,0.3,0.4,0.3,0.7c0,0.2,0.1,0.5,0.1,1v1.4c0,1,0,1.6,0.1,1.9c0,0.3,0.1,0.5,0.3,0.7h-1.1
|
||||
C98.2,389.8,98.1,389.6,98.1,389.3z M98,386.9c-0.4,0.2-1,0.3-1.7,0.4c-0.4,0.1-0.7,0.1-0.9,0.2c-0.2,0.1-0.3,0.2-0.4,0.3
|
||||
c-0.1,0.2-0.1,0.3-0.1,0.5c0,0.3,0.1,0.5,0.3,0.7c0.2,0.2,0.5,0.3,0.9,0.3s0.8-0.1,1.1-0.3c0.3-0.2,0.6-0.4,0.7-0.7
|
||||
c0.1-0.2,0.2-0.6,0.2-1.1V386.9z M100.4,387.3l1.1-0.1c0.1,0.4,0.2,0.8,0.4,1.1c0.2,0.3,0.5,0.5,0.9,0.7c0.4,0.2,0.8,0.3,1.3,0.3
|
||||
c0.4,0,0.8-0.1,1.2-0.2c0.3-0.1,0.6-0.3,0.8-0.5c0.2-0.2,0.2-0.5,0.2-0.7c0-0.3-0.1-0.5-0.2-0.7c-0.2-0.2-0.4-0.4-0.8-0.5
|
||||
c-0.2-0.1-0.7-0.2-1.5-0.4c-0.8-0.2-1.4-0.4-1.7-0.5c-0.4-0.2-0.7-0.5-0.9-0.8c-0.2-0.3-0.3-0.7-0.3-1.1c0-0.4,0.1-0.8,0.4-1.2
|
||||
c0.3-0.4,0.6-0.7,1.1-0.9c0.5-0.2,1-0.3,1.6-0.3c0.6,0,1.2,0.1,1.7,0.3c0.5,0.2,0.9,0.5,1.1,0.9c0.3,0.4,0.4,0.9,0.4,1.4l-1.1,0.1
|
||||
c-0.1-0.5-0.3-1-0.6-1.2c-0.3-0.3-0.8-0.4-1.5-0.4c-0.7,0-1.2,0.1-1.5,0.4c-0.3,0.3-0.5,0.6-0.5,0.9c0,0.3,0.1,0.6,0.3,0.8
|
||||
c0.2,0.2,0.8,0.4,1.7,0.6c0.9,0.2,1.6,0.4,1.9,0.6c0.5,0.2,0.9,0.5,1.1,0.9c0.2,0.4,0.4,0.8,0.4,1.2c0,0.5-0.1,0.9-0.4,1.3
|
||||
c-0.3,0.4-0.6,0.7-1.1,0.9c-0.5,0.2-1,0.3-1.7,0.3c-0.8,0-1.4-0.1-2-0.3c-0.5-0.2-0.9-0.6-1.3-1
|
||||
C100.5,388.4,100.4,387.8,100.4,387.3z M112.6,387.7l1,0.1c-0.1,0.7-0.4,1.3-0.9,1.7c-0.5,0.4-1.1,0.6-1.7,0.6
|
||||
c-0.9,0-1.5-0.3-2.1-0.8c-0.5-0.6-0.8-1.4-0.8-2.4c0-0.7,0.1-1.3,0.3-1.8c0.2-0.5,0.6-0.9,1-1.1c0.5-0.3,1-0.4,1.5-0.4
|
||||
c0.7,0,1.2,0.2,1.7,0.5c0.4,0.3,0.7,0.8,0.8,1.5l-1,0.2c-0.1-0.4-0.3-0.7-0.5-1c-0.3-0.2-0.6-0.3-0.9-0.3c-0.5,0-1,0.2-1.3,0.6
|
||||
c-0.3,0.4-0.5,1-0.5,1.8c0,0.8,0.2,1.5,0.5,1.8c0.3,0.4,0.7,0.6,1.3,0.6c0.4,0,0.8-0.1,1-0.4C112.4,388.7,112.6,388.3,112.6,387.7
|
||||
z M114.5,390v-6.3h1v1c0.2-0.4,0.5-0.7,0.7-0.9c0.2-0.1,0.4-0.2,0.7-0.2c0.4,0,0.7,0.1,1.1,0.3l-0.4,1c-0.3-0.2-0.5-0.2-0.8-0.2
|
||||
c-0.2,0-0.4,0.1-0.6,0.2c-0.2,0.1-0.3,0.3-0.4,0.6c-0.1,0.4-0.2,0.8-0.2,1.2v3.3H114.5z M118.5,382.6v-1.2h1.1v1.2H118.5z
|
||||
M118.5,390v-6.3h1.1v6.3H118.5z M121.1,392.5v-8.7h1v0.8c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.6-0.2,1-0.2c0.5,0,1,0.1,1.4,0.4
|
||||
c0.4,0.3,0.7,0.7,0.9,1.2c0.2,0.5,0.3,1.1,0.3,1.7c0,0.6-0.1,1.2-0.3,1.7c-0.2,0.5-0.6,0.9-1,1.2c-0.4,0.3-0.9,0.4-1.4,0.4
|
||||
c-0.4,0-0.7-0.1-1-0.2c-0.3-0.2-0.5-0.3-0.7-0.6v3.1H121.1z M122.1,386.9c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6
|
||||
c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.9c0-0.8-0.2-1.4-0.5-1.8c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6
|
||||
C122.2,385.5,122.1,386.1,122.1,386.9z M130,389.1l0.1,0.9c-0.3,0.1-0.6,0.1-0.8,0.1c-0.4,0-0.7-0.1-0.9-0.2
|
||||
c-0.2-0.1-0.4-0.3-0.4-0.5c-0.1-0.2-0.1-0.6-0.1-1.3v-3.6h-0.8v-0.8h0.8v-1.6l1.1-0.6v2.2h1.1v0.8H129v3.7c0,0.3,0,0.5,0,0.6
|
||||
c0,0.1,0.1,0.2,0.2,0.2c0.1,0.1,0.2,0.1,0.4,0.1C129.7,389.1,129.8,389.1,130,389.1z M131.7,392.6H131c1.2-1.9,1.8-3.8,1.8-5.7
|
||||
c0-0.7-0.1-1.5-0.2-2.2c-0.1-0.6-0.3-1.2-0.6-1.7c-0.2-0.4-0.5-0.9-0.9-1.8h0.8c0.7,1,1.3,2,1.6,3c0.3,0.9,0.5,1.8,0.5,2.7
|
||||
c0,1.1-0.2,2.1-0.6,3.1C132.8,391,132.3,391.8,131.7,392.6z"/>
|
||||
<path fill="#FFF2CC" d="M209.4,358.9h86.8v43.4h-86.8V358.9z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="round" stroke-miterlimit="10" d="M209.4,358.9h86.8v43.4
|
||||
h-86.8V358.9z"/>
|
||||
<path d="M226.8,373l1.1,0.3c-0.2,0.9-0.7,1.7-1.3,2.2c-0.6,0.5-1.4,0.7-2.3,0.7c-0.9,0-1.7-0.2-2.3-0.6c-0.6-0.4-1-0.9-1.3-1.7
|
||||
c-0.3-0.7-0.5-1.5-0.5-2.3c0-0.9,0.2-1.7,0.5-2.4c0.3-0.7,0.8-1.2,1.5-1.5c0.6-0.4,1.3-0.5,2.1-0.5c0.9,0,1.6,0.2,2.2,0.7
|
||||
c0.6,0.4,1,1.1,1.2,1.9l-1.1,0.3c-0.2-0.6-0.5-1.1-0.9-1.4c-0.4-0.3-0.9-0.4-1.4-0.4c-0.7,0-1.2,0.2-1.7,0.5
|
||||
c-0.4,0.3-0.8,0.8-0.9,1.3c-0.2,0.5-0.3,1.1-0.3,1.7c0,0.7,0.1,1.4,0.3,1.9c0.2,0.5,0.6,1,1,1.2c0.5,0.3,0.9,0.4,1.5,0.4
|
||||
c0.6,0,1.2-0.2,1.6-0.5C226.4,374.3,226.7,373.7,226.8,373z M228.7,372.9c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7
|
||||
c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4
|
||||
c-0.9,0-1.6-0.3-2.1-0.8C229,374.7,228.7,373.9,228.7,372.9z M229.8,372.9c0,0.8,0.2,1.4,0.5,1.8c0.4,0.4,0.8,0.6,1.3,0.6
|
||||
c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8c-0.4-0.4-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6
|
||||
C230,371.5,229.8,372.1,229.8,372.9z M235.3,374.1l1.1-0.2c0.1,0.4,0.2,0.8,0.5,1c0.3,0.2,0.7,0.3,1.1,0.3c0.5,0,0.9-0.1,1.1-0.3
|
||||
c0.2-0.2,0.4-0.4,0.4-0.7c0-0.2-0.1-0.4-0.3-0.6c-0.1-0.1-0.5-0.2-1.1-0.4c-0.8-0.2-1.3-0.4-1.6-0.5c-0.3-0.1-0.5-0.3-0.7-0.6
|
||||
c-0.2-0.3-0.2-0.5-0.2-0.8c0-0.3,0.1-0.5,0.2-0.8c0.1-0.2,0.3-0.4,0.5-0.6c0.2-0.1,0.4-0.2,0.7-0.3c0.3-0.1,0.6-0.1,0.9-0.1
|
||||
c0.5,0,0.9,0.1,1.3,0.2c0.4,0.1,0.6,0.3,0.8,0.6c0.2,0.2,0.3,0.6,0.4,1l-1,0.1c0-0.3-0.2-0.6-0.4-0.7c-0.2-0.2-0.5-0.3-1-0.3
|
||||
c-0.5,0-0.8,0.1-1,0.2c-0.2,0.2-0.3,0.3-0.3,0.6c0,0.1,0,0.3,0.1,0.4c0.1,0.1,0.2,0.2,0.4,0.3c0.1,0,0.4,0.1,0.9,0.3
|
||||
c0.8,0.2,1.3,0.4,1.6,0.5c0.3,0.1,0.5,0.3,0.7,0.6c0.2,0.2,0.3,0.6,0.3,0.9c0,0.4-0.1,0.7-0.3,1c-0.2,0.3-0.5,0.6-0.9,0.7
|
||||
c-0.4,0.2-0.8,0.2-1.3,0.2c-0.8,0-1.4-0.2-1.9-0.5C235.7,375.3,235.4,374.8,235.3,374.1z M241.6,376v-6.3h1v0.9
|
||||
c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.7-0.3,1.1-0.3c0.5,0,0.8,0.1,1.1,0.3c0.3,0.2,0.5,0.5,0.6,0.8c0.5-0.7,1.1-1.1,1.9-1.1
|
||||
c0.6,0,1.1,0.2,1.4,0.5c0.3,0.3,0.5,0.9,0.5,1.6v4.3h-1.1v-4c0-0.4,0-0.7-0.1-0.9c-0.1-0.2-0.2-0.3-0.4-0.5
|
||||
c-0.2-0.1-0.4-0.2-0.6-0.2c-0.4,0-0.8,0.1-1.1,0.4c-0.3,0.3-0.4,0.8-0.4,1.4v3.6h-1.1v-4.1c0-0.5-0.1-0.8-0.3-1.1
|
||||
c-0.2-0.2-0.5-0.4-0.9-0.4c-0.3,0-0.6,0.1-0.8,0.2c-0.3,0.2-0.4,0.4-0.5,0.7c-0.1,0.3-0.2,0.7-0.2,1.3v3.3H241.6z M251.2,372.9
|
||||
c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9
|
||||
c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8C251.4,374.7,251.2,373.9,251.2,372.9z M252.3,372.9
|
||||
c0,0.8,0.2,1.4,0.5,1.8c0.4,0.4,0.8,0.6,1.3,0.6c0.5,0,1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8
|
||||
c-0.4-0.4-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6C252.4,371.5,252.3,372.1,252.3,372.9z M258.2,378.4v-8.7h1v0.8
|
||||
c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.6-0.2,1-0.2c0.5,0,1,0.1,1.4,0.4c0.4,0.3,0.7,0.7,0.9,1.2c0.2,0.5,0.3,1.1,0.3,1.7
|
||||
c0,0.6-0.1,1.2-0.3,1.7c-0.2,0.5-0.6,0.9-1,1.2c-0.4,0.3-0.9,0.4-1.4,0.4c-0.4,0-0.7-0.1-1-0.2c-0.3-0.2-0.5-0.3-0.7-0.6v3.1
|
||||
H258.2z M259.1,372.9c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.9
|
||||
c0-0.8-0.2-1.4-0.5-1.8c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6C259.3,371.5,259.1,372.1,259.1,372.9z M264.4,372.9
|
||||
c0-1.2,0.3-2,1-2.6c0.5-0.5,1.2-0.7,2-0.7c0.9,0,1.6,0.3,2.1,0.9c0.6,0.6,0.8,1.3,0.8,2.3c0,0.8-0.1,1.4-0.4,1.9
|
||||
c-0.2,0.5-0.6,0.8-1.1,1.1c-0.5,0.3-1,0.4-1.5,0.4c-0.9,0-1.6-0.3-2.1-0.8C264.7,374.7,264.4,373.9,264.4,372.9z M265.5,372.9
|
||||
c0,0.8,0.2,1.4,0.5,1.8c0.4,0.4,0.8,0.6,1.3,0.6s1-0.2,1.3-0.6c0.4-0.4,0.5-1,0.5-1.8c0-0.8-0.2-1.4-0.5-1.8
|
||||
c-0.4-0.4-0.8-0.6-1.3-0.6c-0.5,0-1,0.2-1.3,0.6C265.7,371.5,265.5,372.1,265.5,372.9z M271.4,376v-8.7h1.1v8.7H271.4z
|
||||
M274.1,368.6v-1.2h1.1v1.2H274.1z M274.1,376v-6.3h1.1v6.3H274.1z M279,375.1l0.1,0.9c-0.3,0.1-0.6,0.1-0.8,0.1
|
||||
c-0.4,0-0.7-0.1-0.9-0.2c-0.2-0.1-0.4-0.3-0.4-0.5c-0.1-0.2-0.1-0.6-0.1-1.3v-3.6h-0.8v-0.8h0.8v-1.6l1.1-0.6v2.2h1.1v0.8h-1.1
|
||||
v3.7c0,0.3,0,0.5,0,0.6c0,0.1,0.1,0.2,0.2,0.2c0.1,0.1,0.2,0.1,0.4,0.1C278.7,375.1,278.8,375.1,279,375.1z M284.3,374l1.1,0.1
|
||||
c-0.2,0.6-0.5,1.1-1,1.5c-0.5,0.4-1.1,0.5-1.8,0.5c-0.9,0-1.6-0.3-2.2-0.8c-0.5-0.6-0.8-1.4-0.8-2.4c0-1.1,0.3-1.9,0.8-2.5
|
||||
c0.5-0.6,1.3-0.9,2.1-0.9c0.8,0,1.5,0.3,2.1,0.9c0.5,0.6,0.8,1.4,0.8,2.4c0,0.1,0,0.2,0,0.3h-4.7c0,0.7,0.2,1.2,0.6,1.6
|
||||
c0.4,0.4,0.8,0.5,1.3,0.5c0.4,0,0.7-0.1,1-0.3C283.9,374.8,284.1,374.4,284.3,374z M280.8,372.3h3.5c0-0.5-0.2-0.9-0.4-1.2
|
||||
c-0.3-0.4-0.8-0.6-1.3-0.6c-0.5,0-0.9,0.2-1.2,0.5C281,371.3,280.8,371.7,280.8,372.3z"/>
|
||||
<path d="M223.9,392.6c-0.6-0.7-1.1-1.6-1.5-2.6c-0.4-1-0.6-2-0.6-3.1c0-0.9,0.1-1.8,0.4-2.7c0.4-1,0.9-2,1.6-3h0.8
|
||||
c-0.5,0.8-0.8,1.4-0.9,1.8c-0.2,0.5-0.4,1.1-0.6,1.7c-0.2,0.7-0.3,1.5-0.3,2.2c0,1.9,0.6,3.8,1.8,5.7H223.9z M225.3,387.6l1-0.1
|
||||
c0,0.7,0.1,1.1,0.4,1.4c0.2,0.2,0.5,0.4,0.9,0.4c0.3,0,0.5-0.1,0.7-0.2c0.2-0.1,0.4-0.3,0.4-0.5c0.1-0.2,0.1-0.6,0.1-1.1v-6h1.2
|
||||
v5.9c0,0.7-0.1,1.3-0.3,1.7c-0.2,0.4-0.5,0.7-0.8,0.9c-0.4,0.2-0.8,0.3-1.3,0.3c-0.8,0-1.3-0.2-1.8-0.7
|
||||
C225.5,389.1,225.3,388.4,225.3,387.6z M235.8,389.3c-0.4,0.3-0.8,0.6-1.1,0.7c-0.4,0.1-0.8,0.2-1.2,0.2c-0.7,0-1.2-0.2-1.6-0.5
|
||||
c-0.4-0.3-0.6-0.8-0.6-1.3c0-0.3,0.1-0.6,0.2-0.8c0.1-0.3,0.3-0.5,0.5-0.6c0.2-0.2,0.5-0.3,0.8-0.3c0.2-0.1,0.5-0.1,0.9-0.2
|
||||
c0.9-0.1,1.5-0.2,1.9-0.4c0-0.1,0-0.2,0-0.3c0-0.4-0.1-0.7-0.3-0.9c-0.3-0.2-0.7-0.4-1.2-0.4c-0.5,0-0.9,0.1-1.1,0.3
|
||||
c-0.2,0.2-0.4,0.5-0.5,0.9l-1-0.1c0.1-0.4,0.3-0.8,0.5-1.1c0.2-0.3,0.5-0.5,0.9-0.6c0.4-0.2,0.9-0.2,1.4-0.2c0.5,0,1,0.1,1.3,0.2
|
||||
c0.3,0.1,0.6,0.3,0.7,0.5c0.2,0.2,0.3,0.4,0.3,0.7c0,0.2,0.1,0.5,0.1,1v1.4c0,1,0,1.6,0.1,1.9c0,0.3,0.1,0.5,0.3,0.7H236
|
||||
C235.9,389.8,235.9,389.6,235.8,389.3z M235.8,386.9c-0.4,0.2-1,0.3-1.7,0.4c-0.4,0.1-0.7,0.1-0.9,0.2c-0.2,0.1-0.3,0.2-0.4,0.3
|
||||
c-0.1,0.2-0.1,0.3-0.1,0.5c0,0.3,0.1,0.5,0.3,0.7c0.2,0.2,0.5,0.3,0.9,0.3c0.4,0,0.8-0.1,1.1-0.3c0.3-0.2,0.6-0.4,0.7-0.7
|
||||
c0.1-0.2,0.2-0.6,0.2-1.1V386.9z M240.1,390l-2.4-6.3h1.1l1.4,3.8c0.1,0.4,0.3,0.8,0.4,1.3c0.1-0.3,0.2-0.7,0.4-1.2l1.4-3.8h1.1
|
||||
l-2.4,6.3H240.1z M248.4,389.3c-0.4,0.3-0.8,0.6-1.1,0.7c-0.4,0.1-0.8,0.2-1.2,0.2c-0.7,0-1.2-0.2-1.6-0.5
|
||||
c-0.4-0.3-0.6-0.8-0.6-1.3c0-0.3,0.1-0.6,0.2-0.8c0.1-0.3,0.3-0.5,0.5-0.6c0.2-0.2,0.5-0.3,0.8-0.3c0.2-0.1,0.5-0.1,0.9-0.2
|
||||
c0.9-0.1,1.5-0.2,1.9-0.4c0-0.1,0-0.2,0-0.3c0-0.4-0.1-0.7-0.3-0.9c-0.3-0.2-0.7-0.4-1.2-0.4c-0.5,0-0.9,0.1-1.1,0.3
|
||||
c-0.2,0.2-0.4,0.5-0.5,0.9l-1-0.1c0.1-0.4,0.3-0.8,0.5-1.1c0.2-0.3,0.5-0.5,0.9-0.6c0.4-0.2,0.9-0.2,1.4-0.2c0.5,0,1,0.1,1.3,0.2
|
||||
c0.3,0.1,0.6,0.3,0.7,0.5c0.2,0.2,0.3,0.4,0.3,0.7c0,0.2,0.1,0.5,0.1,1v1.4c0,1,0,1.6,0.1,1.9c0,0.3,0.1,0.5,0.3,0.7h-1.1
|
||||
C248.5,389.8,248.4,389.6,248.4,389.3z M248.3,386.9c-0.4,0.2-1,0.3-1.7,0.4c-0.4,0.1-0.7,0.1-0.9,0.2c-0.2,0.1-0.3,0.2-0.4,0.3
|
||||
c-0.1,0.2-0.1,0.3-0.1,0.5c0,0.3,0.1,0.5,0.3,0.7c0.2,0.2,0.5,0.3,0.9,0.3c0.4,0,0.8-0.1,1.1-0.3c0.3-0.2,0.6-0.4,0.7-0.7
|
||||
c0.1-0.2,0.2-0.6,0.2-1.1V386.9z M250.7,387.3l1.1-0.1c0.1,0.4,0.2,0.8,0.4,1.1c0.2,0.3,0.5,0.5,0.9,0.7c0.4,0.2,0.8,0.3,1.3,0.3
|
||||
c0.4,0,0.8-0.1,1.2-0.2c0.3-0.1,0.6-0.3,0.8-0.5c0.2-0.2,0.2-0.5,0.2-0.7c0-0.3-0.1-0.5-0.2-0.7c-0.2-0.2-0.4-0.4-0.8-0.5
|
||||
c-0.2-0.1-0.7-0.2-1.5-0.4c-0.8-0.2-1.4-0.4-1.7-0.5c-0.4-0.2-0.7-0.5-0.9-0.8c-0.2-0.3-0.3-0.7-0.3-1.1c0-0.4,0.1-0.8,0.4-1.2
|
||||
c0.3-0.4,0.6-0.7,1.1-0.9c0.5-0.2,1-0.3,1.6-0.3c0.6,0,1.2,0.1,1.7,0.3c0.5,0.2,0.9,0.5,1.1,0.9c0.3,0.4,0.4,0.9,0.4,1.4l-1.1,0.1
|
||||
c-0.1-0.5-0.3-1-0.6-1.2c-0.3-0.3-0.8-0.4-1.5-0.4c-0.7,0-1.2,0.1-1.5,0.4c-0.3,0.3-0.5,0.6-0.5,0.9c0,0.3,0.1,0.6,0.3,0.8
|
||||
c0.2,0.2,0.8,0.4,1.7,0.6c0.9,0.2,1.6,0.4,1.9,0.6c0.5,0.2,0.9,0.5,1.1,0.9c0.2,0.4,0.4,0.8,0.4,1.2c0,0.5-0.1,0.9-0.4,1.3
|
||||
c-0.3,0.4-0.6,0.7-1.1,0.9c-0.5,0.2-1,0.3-1.7,0.3c-0.8,0-1.4-0.1-2-0.3c-0.5-0.2-0.9-0.6-1.3-1
|
||||
C250.8,388.4,250.7,387.8,250.7,387.3z M262.9,387.7l1,0.1c-0.1,0.7-0.4,1.3-0.9,1.7c-0.5,0.4-1.1,0.6-1.7,0.6
|
||||
c-0.9,0-1.5-0.3-2.1-0.8c-0.5-0.6-0.8-1.4-0.8-2.4c0-0.7,0.1-1.3,0.3-1.8c0.2-0.5,0.6-0.9,1-1.1c0.5-0.3,1-0.4,1.5-0.4
|
||||
c0.7,0,1.2,0.2,1.7,0.5c0.4,0.3,0.7,0.8,0.8,1.5l-1,0.2c-0.1-0.4-0.3-0.7-0.5-1s-0.6-0.3-0.9-0.3c-0.5,0-1,0.2-1.3,0.6
|
||||
c-0.3,0.4-0.5,1-0.5,1.8c0,0.8,0.2,1.5,0.5,1.8c0.3,0.4,0.7,0.6,1.3,0.6c0.4,0,0.8-0.1,1-0.4C262.7,388.7,262.9,388.3,262.9,387.7
|
||||
z M264.8,390v-6.3h1v1c0.2-0.4,0.5-0.7,0.7-0.9c0.2-0.1,0.4-0.2,0.7-0.2c0.4,0,0.7,0.1,1.1,0.3l-0.4,1c-0.3-0.2-0.5-0.2-0.8-0.2
|
||||
c-0.2,0-0.4,0.1-0.6,0.2c-0.2,0.1-0.3,0.3-0.4,0.6c-0.1,0.4-0.2,0.8-0.2,1.2v3.3H264.8z M268.8,382.6v-1.2h1.1v1.2H268.8z
|
||||
M268.8,390v-6.3h1.1v6.3H268.8z M271.4,392.5v-8.7h1v0.8c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.6-0.2,1-0.2c0.5,0,1,0.1,1.4,0.4
|
||||
c0.4,0.3,0.7,0.7,0.9,1.2c0.2,0.5,0.3,1.1,0.3,1.7c0,0.6-0.1,1.2-0.3,1.7c-0.2,0.5-0.6,0.9-1,1.2c-0.4,0.3-0.9,0.4-1.4,0.4
|
||||
c-0.4,0-0.7-0.1-1-0.2c-0.3-0.2-0.5-0.3-0.7-0.6v3.1H271.4z M272.4,386.9c0,0.8,0.2,1.4,0.5,1.8c0.3,0.4,0.7,0.6,1.2,0.6
|
||||
c0.5,0,0.9-0.2,1.2-0.6c0.3-0.4,0.5-1,0.5-1.9c0-0.8-0.2-1.4-0.5-1.8c-0.3-0.4-0.7-0.6-1.2-0.6c-0.5,0-0.9,0.2-1.2,0.6
|
||||
C272.5,385.5,272.4,386.1,272.4,386.9z M280.3,389.1l0.1,0.9c-0.3,0.1-0.6,0.1-0.8,0.1c-0.4,0-0.7-0.1-0.9-0.2
|
||||
c-0.2-0.1-0.4-0.3-0.4-0.5c-0.1-0.2-0.1-0.6-0.1-1.3v-3.6h-0.8v-0.8h0.8v-1.6l1.1-0.6v2.2h1.1v0.8h-1.1v3.7c0,0.3,0,0.5,0,0.6
|
||||
c0,0.1,0.1,0.2,0.2,0.2c0.1,0.1,0.2,0.1,0.4,0.1C280,389.1,280.1,389.1,280.3,389.1z M282,392.6h-0.8c1.2-1.9,1.8-3.8,1.8-5.7
|
||||
c0-0.7-0.1-1.5-0.2-2.2c-0.1-0.6-0.3-1.2-0.6-1.7c-0.2-0.4-0.5-0.9-0.9-1.8h0.8c0.7,1,1.3,2,1.6,3c0.3,0.9,0.5,1.8,0.5,2.7
|
||||
c0,1.1-0.2,2.1-0.6,3.1C283.1,391,282.6,391.8,282,392.6z"/>
|
||||
<path fill-opacity="0" d="M145.9,380.5h63.5"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="round" stroke-miterlimit="10" d="M153.5,380.5h48.2"/>
|
||||
<path stroke="#000000" stroke-width="2" stroke-miterlimit="10" d="M153.5,378.4l-5.8,2.1l5.8,2.1V378.4z"/>
|
||||
<path stroke="#000000" stroke-width="2" stroke-miterlimit="10" d="M201.7,382.6l5.8-2.1l-5.8-2.1V382.6z"/>
|
||||
<path fill-opacity="0" d="M145.9,358.9h63.5v21.7h-63.5V358.9z"/>
|
||||
<path d="M158,366l0.7-0.1c0,0.5,0.1,0.8,0.2,0.9c0.2,0.2,0.4,0.2,0.6,0.2c0.2,0,0.4,0,0.5-0.1c0.1-0.1,0.2-0.2,0.3-0.4
|
||||
c0.1-0.2,0.1-0.4,0.1-0.7v-4.1h0.8v4c0,0.5-0.1,0.9-0.2,1.2c-0.1,0.3-0.3,0.5-0.6,0.6c-0.3,0.1-0.6,0.2-0.9,0.2
|
||||
c-0.5,0-0.9-0.1-1.2-0.4C158.1,367,158,366.6,158,366z M165.4,367.1c-0.3,0.2-0.5,0.4-0.8,0.5c-0.2,0.1-0.5,0.1-0.8,0.1
|
||||
c-0.5,0-0.8-0.1-1.1-0.3c-0.3-0.2-0.4-0.5-0.4-0.9c0-0.2,0-0.4,0.1-0.6c0.1-0.2,0.2-0.3,0.4-0.4c0.2-0.1,0.3-0.2,0.5-0.2
|
||||
c0.1,0,0.4-0.1,0.6-0.1c0.6-0.1,1-0.1,1.3-0.2c0-0.1,0-0.2,0-0.2c0-0.3-0.1-0.5-0.2-0.6c-0.2-0.2-0.5-0.2-0.8-0.2
|
||||
c-0.3,0-0.6,0.1-0.8,0.2c-0.2,0.1-0.3,0.3-0.4,0.6l-0.7-0.1c0.1-0.3,0.2-0.6,0.3-0.7c0.2-0.2,0.4-0.3,0.6-0.4
|
||||
c0.3-0.1,0.6-0.1,1-0.1c0.4,0,0.7,0,0.9,0.1c0.2,0.1,0.4,0.2,0.5,0.3c0.1,0.1,0.2,0.3,0.2,0.5c0,0.1,0,0.4,0,0.7v1
|
||||
c0,0.7,0,1.1,0,1.3c0,0.2,0.1,0.4,0.2,0.5h-0.8C165.4,367.5,165.4,367.3,165.4,367.1z M165.3,365.5c-0.3,0.1-0.7,0.2-1.2,0.3
|
||||
c-0.3,0-0.5,0.1-0.6,0.1c-0.1,0.1-0.2,0.1-0.3,0.2c-0.1,0.1-0.1,0.2-0.1,0.3c0,0.2,0.1,0.4,0.2,0.5c0.1,0.1,0.4,0.2,0.6,0.2
|
||||
c0.3,0,0.5-0.1,0.7-0.2c0.2-0.1,0.4-0.3,0.5-0.5c0.1-0.2,0.1-0.4,0.1-0.7V365.5z M168.5,367.7l-1.6-4.3h0.8l0.9,2.6
|
||||
c0.1,0.3,0.2,0.6,0.3,0.9c0.1-0.2,0.2-0.5,0.3-0.8l1-2.6h0.7l-1.6,4.3H168.5z M174.3,367.1c-0.3,0.2-0.5,0.4-0.8,0.5
|
||||
c-0.2,0.1-0.5,0.1-0.8,0.1c-0.5,0-0.8-0.1-1.1-0.3c-0.3-0.2-0.4-0.5-0.4-0.9c0-0.2,0-0.4,0.1-0.6c0.1-0.2,0.2-0.3,0.4-0.4
|
||||
c0.2-0.1,0.3-0.2,0.5-0.2c0.1,0,0.4-0.1,0.6-0.1c0.6-0.1,1-0.1,1.3-0.2c0-0.1,0-0.2,0-0.2c0-0.3-0.1-0.5-0.2-0.6
|
||||
c-0.2-0.2-0.5-0.2-0.8-0.2c-0.3,0-0.6,0.1-0.8,0.2c-0.2,0.1-0.3,0.3-0.4,0.6l-0.7-0.1c0.1-0.3,0.2-0.6,0.3-0.7
|
||||
c0.2-0.2,0.4-0.3,0.6-0.4c0.3-0.1,0.6-0.1,1-0.1c0.4,0,0.7,0,0.9,0.1c0.2,0.1,0.4,0.2,0.5,0.3c0.1,0.1,0.2,0.3,0.2,0.5
|
||||
c0,0.1,0,0.4,0,0.7v1c0,0.7,0,1.1,0,1.3c0,0.2,0.1,0.4,0.2,0.5h-0.8C174.4,367.5,174.4,367.3,174.3,367.1z M174.3,365.5
|
||||
c-0.3,0.1-0.7,0.2-1.2,0.3c-0.3,0-0.5,0.1-0.6,0.1c-0.1,0.1-0.2,0.1-0.3,0.2c-0.1,0.1-0.1,0.2-0.1,0.3c0,0.2,0.1,0.4,0.2,0.5
|
||||
c0.1,0.1,0.4,0.2,0.6,0.2c0.3,0,0.5-0.1,0.7-0.2c0.2-0.1,0.4-0.3,0.5-0.5c0.1-0.2,0.1-0.4,0.1-0.7V365.5z M176.1,365.8l0.7-0.1
|
||||
c0,0.3,0.1,0.5,0.2,0.7c0.1,0.2,0.3,0.3,0.6,0.5c0.3,0.1,0.6,0.2,0.9,0.2c0.3,0,0.6,0,0.8-0.1c0.2-0.1,0.4-0.2,0.5-0.4
|
||||
c0.1-0.2,0.2-0.3,0.2-0.5s-0.1-0.3-0.2-0.5c-0.1-0.1-0.3-0.3-0.5-0.3c-0.2-0.1-0.5-0.2-1.1-0.3c-0.5-0.1-0.9-0.3-1.1-0.4
|
||||
c-0.3-0.1-0.5-0.3-0.6-0.5c-0.1-0.2-0.2-0.5-0.2-0.7c0-0.3,0.1-0.6,0.2-0.8c0.2-0.3,0.4-0.5,0.7-0.6c0.3-0.1,0.7-0.2,1.1-0.2
|
||||
c0.4,0,0.8,0.1,1.2,0.2c0.3,0.1,0.6,0.3,0.8,0.6c0.2,0.3,0.3,0.6,0.3,0.9l-0.8,0.1c0-0.4-0.2-0.7-0.4-0.8c-0.2-0.2-0.6-0.3-1-0.3
|
||||
c-0.5,0-0.8,0.1-1,0.3c-0.2,0.2-0.3,0.4-0.3,0.6c0,0.2,0.1,0.4,0.2,0.5c0.2,0.1,0.5,0.3,1.2,0.4c0.6,0.1,1.1,0.3,1.3,0.4
|
||||
c0.3,0.2,0.6,0.4,0.8,0.6c0.2,0.2,0.2,0.5,0.2,0.8c0,0.3-0.1,0.6-0.3,0.9c-0.2,0.3-0.4,0.5-0.8,0.7c-0.3,0.2-0.7,0.2-1.1,0.2
|
||||
c-0.5,0-1-0.1-1.4-0.2c-0.4-0.2-0.6-0.4-0.8-0.7C176.2,366.5,176.1,366.1,176.1,365.8z M184.7,366.1l0.7,0.1
|
||||
c-0.1,0.5-0.3,0.9-0.6,1.2c-0.3,0.3-0.7,0.4-1.2,0.4c-0.6,0-1.1-0.2-1.4-0.6c-0.4-0.4-0.5-0.9-0.5-1.7c0-0.5,0.1-0.9,0.2-1.2
|
||||
c0.2-0.4,0.4-0.6,0.7-0.8s0.7-0.3,1-0.3c0.5,0,0.8,0.1,1.1,0.4c0.3,0.2,0.5,0.6,0.6,1l-0.7,0.1c-0.1-0.3-0.2-0.5-0.4-0.6
|
||||
c-0.2-0.1-0.4-0.2-0.6-0.2c-0.4,0-0.7,0.1-0.9,0.4c-0.2,0.3-0.3,0.7-0.3,1.2c0,0.6,0.1,1,0.3,1.3c0.2,0.3,0.5,0.4,0.9,0.4
|
||||
c0.3,0,0.5-0.1,0.7-0.3C184.6,366.7,184.7,366.4,184.7,366.1z M186.2,367.7v-4.3h0.7v0.6c0.2-0.3,0.3-0.5,0.5-0.6
|
||||
c0.1-0.1,0.3-0.1,0.5-0.1c0.2,0,0.5,0.1,0.7,0.2l-0.2,0.7c-0.2-0.1-0.4-0.2-0.5-0.2c-0.2,0-0.3,0-0.4,0.1
|
||||
c-0.1,0.1-0.2,0.2-0.3,0.4c-0.1,0.3-0.1,0.5-0.1,0.8v2.3H186.2z M189,362.6v-0.8h0.7v0.8H189z M189,367.7v-4.3h0.7v4.3H189z
|
||||
M190.9,369.3v-5.9h0.7v0.6c0.2-0.2,0.3-0.4,0.5-0.5c0.2-0.1,0.4-0.2,0.7-0.2c0.4,0,0.7,0.1,1,0.3c0.3,0.2,0.5,0.5,0.6,0.8
|
||||
c0.1,0.3,0.2,0.7,0.2,1.1c0,0.4-0.1,0.8-0.2,1.2c-0.2,0.4-0.4,0.6-0.7,0.8c-0.3,0.2-0.6,0.3-0.9,0.3c-0.2,0-0.5,0-0.6-0.1
|
||||
c-0.2-0.1-0.4-0.2-0.5-0.4v2.1H190.9z M191.6,365.5c0,0.6,0.1,1,0.3,1.2c0.2,0.3,0.5,0.4,0.8,0.4c0.3,0,0.6-0.1,0.8-0.4
|
||||
c0.2-0.3,0.3-0.7,0.3-1.3c0-0.6-0.1-1-0.3-1.2c-0.2-0.3-0.5-0.4-0.8-0.4s-0.6,0.1-0.8,0.4C191.7,364.6,191.6,365,191.6,365.5z
|
||||
M197.2,367l0.1,0.6c-0.2,0-0.4,0.1-0.5,0.1c-0.3,0-0.5,0-0.6-0.1c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.1-0.4-0.1-0.9v-2.5h-0.5
|
||||
v-0.6h0.5v-1.1l0.7-0.4v1.5h0.7v0.6h-0.7v2.5c0,0.2,0,0.3,0,0.4s0.1,0.1,0.1,0.1c0.1,0,0.1,0,0.2,0C197,367,197.1,367,197.2,367z"
|
||||
/>
|
||||
<path d="M170.7,377.9l2.3-5.9h0.8l2.4,5.9h-0.9l-0.7-1.8h-2.5l-0.7,1.8H170.7z M172.5,375.4h2l-0.6-1.6c-0.2-0.5-0.3-0.9-0.4-1.2
|
||||
c-0.1,0.4-0.2,0.8-0.3,1.1L172.5,375.4z M177.1,377.9v-5.9h2.2c0.4,0,0.7,0,0.9,0.1c0.3,0,0.5,0.1,0.7,0.3
|
||||
c0.2,0.1,0.4,0.3,0.5,0.6c0.1,0.2,0.2,0.5,0.2,0.8c0,0.5-0.2,0.9-0.5,1.3c-0.3,0.3-0.9,0.5-1.7,0.5h-1.5v2.4H177.1z M177.9,374.7
|
||||
h1.5c0.5,0,0.9-0.1,1.1-0.3c0.2-0.2,0.3-0.5,0.3-0.8c0-0.2-0.1-0.5-0.2-0.6c-0.1-0.2-0.3-0.3-0.5-0.4c-0.1,0-0.4,0-0.7,0h-1.5
|
||||
V374.7z M182.9,377.9v-5.9h0.8v5.9H182.9z"/>
|
||||
<path fill-opacity="0" d="M201.5,282.9l51.2,75.9"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="round" stroke-miterlimit="10" d="M205.8,289.3l42.7,63.3"
|
||||
/>
|
||||
<path stroke="#000000" stroke-width="2" stroke-miterlimit="10" d="M207.6,288.1l-5-3.6l1.5,6L207.6,288.1z"/>
|
||||
<path stroke="#000000" stroke-width="2" stroke-miterlimit="10" d="M246.7,353.7l5,3.6l-1.5-6L246.7,353.7z"/>
|
||||
<path fill-opacity="0" d="M222.2,302.3h113.3v30.6H222.2V302.3z"/>
|
||||
<path d="M228.6,316.2v-5.9h0.8v2.4h3.1v-2.4h0.8v5.9h-0.8v-2.8h-3.1v2.8H228.6z M236.2,316.2V311h-2v-0.7h4.7v0.7h-2v5.2H236.2z
|
||||
M241.4,316.2V311h-2v-0.7h4.7v0.7h-2v5.2H241.4z M245.1,316.2v-5.9h2.2c0.4,0,0.7,0,0.9,0.1c0.3,0,0.5,0.1,0.7,0.3
|
||||
c0.2,0.1,0.4,0.3,0.5,0.6s0.2,0.5,0.2,0.8c0,0.5-0.2,0.9-0.5,1.3c-0.3,0.3-0.9,0.5-1.7,0.5h-1.5v2.4H245.1z M245.9,313.1h1.5
|
||||
c0.5,0,0.9-0.1,1.1-0.3c0.2-0.2,0.3-0.5,0.3-0.8c0-0.2-0.1-0.5-0.2-0.6c-0.1-0.2-0.3-0.3-0.5-0.4c-0.1,0-0.4,0-0.7,0h-1.5V313.1z
|
||||
M252.7,314.5l0.7-0.1c0,0.5,0.1,0.8,0.2,0.9c0.2,0.2,0.4,0.2,0.6,0.2c0.2,0,0.4,0,0.5-0.1c0.1-0.1,0.2-0.2,0.3-0.4
|
||||
c0.1-0.2,0.1-0.4,0.1-0.7v-4.1h0.8v4c0,0.5-0.1,0.9-0.2,1.2c-0.1,0.3-0.3,0.5-0.6,0.6c-0.3,0.1-0.6,0.2-0.9,0.2
|
||||
c-0.5,0-0.9-0.1-1.2-0.4C252.8,315.6,252.7,315.1,252.7,314.5z M257.1,314.3l0.7-0.1c0,0.3,0.1,0.5,0.2,0.7
|
||||
c0.1,0.2,0.3,0.3,0.6,0.5c0.3,0.1,0.6,0.2,0.9,0.2c0.3,0,0.6,0,0.8-0.1c0.2-0.1,0.4-0.2,0.5-0.4c0.1-0.2,0.2-0.3,0.2-0.5
|
||||
c0-0.2-0.1-0.3-0.2-0.5c-0.1-0.1-0.3-0.3-0.5-0.3c-0.2-0.1-0.5-0.2-1.1-0.3s-0.9-0.3-1.1-0.4c-0.3-0.1-0.5-0.3-0.6-0.5
|
||||
c-0.1-0.2-0.2-0.5-0.2-0.7c0-0.3,0.1-0.6,0.2-0.8c0.2-0.3,0.4-0.5,0.7-0.6c0.3-0.1,0.7-0.2,1.1-0.2c0.4,0,0.8,0.1,1.2,0.2
|
||||
c0.3,0.1,0.6,0.3,0.8,0.6c0.2,0.3,0.3,0.6,0.3,0.9l-0.8,0.1c0-0.4-0.2-0.7-0.4-0.8c-0.2-0.2-0.6-0.3-1-0.3c-0.5,0-0.8,0.1-1,0.3
|
||||
c-0.2,0.2-0.3,0.4-0.3,0.6c0,0.2,0.1,0.4,0.2,0.5c0.2,0.1,0.5,0.3,1.2,0.4c0.6,0.1,1.1,0.3,1.3,0.4c0.3,0.2,0.6,0.4,0.8,0.6
|
||||
c0.2,0.2,0.2,0.5,0.2,0.8c0,0.3-0.1,0.6-0.3,0.9c-0.2,0.3-0.4,0.5-0.8,0.7c-0.3,0.2-0.7,0.2-1.1,0.2c-0.5,0-1-0.1-1.4-0.2
|
||||
c-0.4-0.2-0.6-0.4-0.8-0.7C257.2,315.1,257.1,314.7,257.1,314.3z M262.8,313.3c0-1,0.3-1.8,0.8-2.3c0.5-0.6,1.2-0.8,2.1-0.8
|
||||
c0.6,0,1,0.1,1.5,0.4c0.4,0.3,0.8,0.6,1,1.1c0.2,0.5,0.3,1,0.3,1.6c0,0.6-0.1,1.1-0.4,1.6c-0.2,0.5-0.6,0.8-1,1.1
|
||||
c-0.4,0.2-0.9,0.4-1.4,0.4c-0.6,0-1.1-0.1-1.5-0.4c-0.4-0.3-0.8-0.6-1-1.1C262.9,314.3,262.8,313.9,262.8,313.3z M263.6,313.3
|
||||
c0,0.7,0.2,1.3,0.6,1.7c0.4,0.4,0.9,0.6,1.5,0.6c0.6,0,1.1-0.2,1.5-0.6c0.4-0.4,0.6-1,0.6-1.8c0-0.5-0.1-0.9-0.2-1.3
|
||||
c-0.2-0.4-0.4-0.6-0.7-0.8c-0.3-0.2-0.7-0.3-1.1-0.3c-0.6,0-1,0.2-1.4,0.6C263.8,311.8,263.6,312.5,263.6,313.3z M269.6,316.2
|
||||
v-5.9h0.8l3.1,4.7v-4.7h0.8v5.9h-0.8l-3.1-4.7v4.7H269.6z M277.5,316.2l2.3-5.9h0.8l2.4,5.9h-0.9l-0.7-1.8H279l-0.7,1.8H277.5z
|
||||
M279.2,313.8h2l-0.6-1.6c-0.2-0.5-0.3-0.9-0.4-1.2c-0.1,0.4-0.2,0.8-0.3,1.1L279.2,313.8z M283.8,316.2v-5.9h2.2
|
||||
c0.4,0,0.7,0,0.9,0.1c0.3,0,0.5,0.1,0.7,0.3c0.2,0.1,0.4,0.3,0.5,0.6s0.2,0.5,0.2,0.8c0,0.5-0.2,0.9-0.5,1.3
|
||||
c-0.3,0.3-0.9,0.5-1.7,0.5h-1.5v2.4H283.8z M284.6,313.1h1.5c0.5,0,0.9-0.1,1.1-0.3c0.2-0.2,0.3-0.5,0.3-0.8
|
||||
c0-0.2-0.1-0.5-0.2-0.6c-0.1-0.2-0.3-0.3-0.5-0.4c-0.1,0-0.4,0-0.7,0h-1.5V313.1z M289.6,316.2v-5.9h0.8v5.9H289.6z"/>
|
||||
<path d="M236.1,324.1v-0.7l2.5,0v2.2c-0.4,0.3-0.8,0.5-1.2,0.7c-0.4,0.2-0.8,0.2-1.3,0.2c-0.6,0-1.1-0.1-1.6-0.4
|
||||
c-0.5-0.3-0.8-0.6-1.1-1.1c-0.2-0.5-0.4-1-0.4-1.6c0-0.6,0.1-1.1,0.4-1.6c0.2-0.5,0.6-0.9,1-1.1c0.5-0.2,1-0.4,1.6-0.4
|
||||
c0.4,0,0.8,0.1,1.2,0.2c0.3,0.1,0.6,0.3,0.8,0.6c0.2,0.2,0.3,0.6,0.4,1l-0.7,0.2c-0.1-0.3-0.2-0.5-0.3-0.7
|
||||
c-0.1-0.2-0.3-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2c-0.4,0-0.7,0.1-0.9,0.2c-0.3,0.1-0.5,0.2-0.6,0.4c-0.2,0.2-0.3,0.4-0.4,0.6
|
||||
c-0.1,0.4-0.2,0.8-0.2,1.2c0,0.5,0.1,1,0.3,1.3c0.2,0.4,0.4,0.6,0.8,0.8c0.4,0.2,0.7,0.3,1.1,0.3c0.3,0,0.7-0.1,1-0.2
|
||||
c0.3-0.1,0.6-0.3,0.7-0.4v-1.1H236.1z M239.5,324.3c0-0.8,0.2-1.4,0.7-1.8c0.4-0.3,0.8-0.5,1.4-0.5c0.6,0,1.1,0.2,1.4,0.6
|
||||
c0.4,0.4,0.6,0.9,0.6,1.6c0,0.6-0.1,1-0.2,1.3c-0.2,0.3-0.4,0.6-0.7,0.7c-0.3,0.2-0.7,0.3-1,0.3c-0.6,0-1.1-0.2-1.5-0.6
|
||||
C239.7,325.6,239.5,325,239.5,324.3z M240.3,324.3c0,0.6,0.1,1,0.4,1.2c0.2,0.3,0.5,0.4,0.9,0.4c0.4,0,0.7-0.1,0.9-0.4
|
||||
c0.2-0.3,0.4-0.7,0.4-1.3c0-0.5-0.1-0.9-0.4-1.2c-0.2-0.3-0.5-0.4-0.9-0.4c-0.4,0-0.7,0.1-0.9,0.4S240.3,323.7,240.3,324.3z
|
||||
M244.3,324.3c0-0.8,0.2-1.4,0.7-1.8c0.4-0.3,0.8-0.5,1.4-0.5c0.6,0,1.1,0.2,1.4,0.6c0.4,0.4,0.6,0.9,0.6,1.6c0,0.6-0.1,1-0.2,1.3
|
||||
c-0.2,0.3-0.4,0.6-0.7,0.7c-0.3,0.2-0.7,0.3-1,0.3c-0.6,0-1.1-0.2-1.5-0.6C244.4,325.6,244.3,325,244.3,324.3z M245,324.3
|
||||
c0,0.6,0.1,1,0.4,1.2c0.2,0.3,0.5,0.4,0.9,0.4c0.4,0,0.7-0.1,0.9-0.4c0.2-0.3,0.4-0.7,0.4-1.3c0-0.5-0.1-0.9-0.4-1.2
|
||||
c-0.2-0.3-0.5-0.4-0.9-0.4c-0.4,0-0.7,0.1-0.9,0.4S245,323.7,245,324.3z M249.1,326.8l0.7,0.1c0,0.2,0.1,0.4,0.2,0.5
|
||||
c0.2,0.1,0.4,0.2,0.7,0.2c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.3-0.3,0.4-0.6c0-0.1,0.1-0.5,0.1-0.9c-0.3,0.4-0.7,0.6-1.2,0.6
|
||||
c-0.6,0-1-0.2-1.4-0.6c-0.3-0.4-0.5-0.9-0.5-1.5c0-0.4,0.1-0.8,0.2-1.1c0.2-0.3,0.4-0.6,0.6-0.8c0.3-0.2,0.6-0.3,1-0.3
|
||||
c0.5,0,0.9,0.2,1.3,0.6v-0.5h0.7v3.7c0,0.7-0.1,1.1-0.2,1.4c-0.1,0.3-0.3,0.5-0.6,0.7c-0.3,0.2-0.7,0.2-1.1,0.2
|
||||
c-0.5,0-0.9-0.1-1.2-0.3C249.3,327.6,249.1,327.2,249.1,326.8z M249.7,324.2c0,0.6,0.1,1,0.3,1.2c0.2,0.3,0.5,0.4,0.8,0.4
|
||||
c0.3,0,0.6-0.1,0.8-0.4c0.2-0.3,0.3-0.7,0.3-1.2c0-0.5-0.1-0.9-0.3-1.2c-0.2-0.3-0.5-0.4-0.8-0.4c-0.3,0-0.6,0.1-0.8,0.4
|
||||
C249.8,323.3,249.7,323.7,249.7,324.2z M254,326.4v-5.9h0.7v5.9H254z M258.8,325l0.8,0.1c-0.1,0.4-0.3,0.8-0.7,1
|
||||
c-0.3,0.2-0.7,0.4-1.2,0.4c-0.6,0-1.1-0.2-1.5-0.6c-0.4-0.4-0.5-0.9-0.5-1.6c0-0.7,0.2-1.3,0.6-1.7c0.4-0.4,0.9-0.6,1.5-0.6
|
||||
c0.6,0,1,0.2,1.4,0.6c0.4,0.4,0.5,0.9,0.5,1.6c0,0,0,0.1,0,0.2h-3.2c0,0.5,0.2,0.8,0.4,1.1c0.2,0.3,0.5,0.4,0.9,0.4
|
||||
c0.3,0,0.5-0.1,0.7-0.2C258.5,325.6,258.7,325.3,258.8,325z M256.4,323.9h2.4c0-0.4-0.1-0.6-0.3-0.8c-0.2-0.3-0.5-0.4-0.9-0.4
|
||||
c-0.3,0-0.6,0.1-0.8,0.3C256.6,323.2,256.4,323.5,256.4,323.9z M267.3,324.3l0.8,0.2c-0.2,0.6-0.5,1.1-0.9,1.5
|
||||
c-0.4,0.3-0.9,0.5-1.6,0.5c-0.6,0-1.2-0.1-1.6-0.4c-0.4-0.3-0.7-0.6-0.9-1.1c-0.2-0.5-0.3-1-0.3-1.6c0-0.6,0.1-1.2,0.4-1.6
|
||||
c0.2-0.5,0.6-0.8,1-1.1c0.4-0.2,0.9-0.4,1.4-0.4c0.6,0,1.1,0.2,1.5,0.5c0.4,0.3,0.7,0.7,0.8,1.3l-0.8,0.2
|
||||
c-0.1-0.4-0.3-0.7-0.6-0.9c-0.3-0.2-0.6-0.3-1-0.3c-0.5,0-0.8,0.1-1.1,0.3c-0.3,0.2-0.5,0.5-0.6,0.9c-0.1,0.4-0.2,0.7-0.2,1.1
|
||||
c0,0.5,0.1,0.9,0.2,1.3s0.4,0.7,0.7,0.8c0.3,0.2,0.7,0.3,1,0.3c0.4,0,0.8-0.1,1.1-0.4C267,325.2,267.2,324.8,267.3,324.3z
|
||||
M269.1,326.4v-5.9h0.7v2.1c0.3-0.4,0.8-0.6,1.3-0.6c0.3,0,0.6,0.1,0.8,0.2c0.2,0.1,0.4,0.3,0.5,0.5c0.1,0.2,0.1,0.5,0.1,1v2.7
|
||||
h-0.7v-2.7c0-0.4-0.1-0.6-0.2-0.8c-0.2-0.2-0.4-0.2-0.7-0.2c-0.2,0-0.4,0.1-0.6,0.2c-0.2,0.1-0.3,0.3-0.4,0.4
|
||||
c-0.1,0.2-0.1,0.5-0.1,0.8v2.4H269.1z M276.6,325.9c-0.3,0.2-0.5,0.4-0.8,0.5c-0.2,0.1-0.5,0.1-0.8,0.1c-0.5,0-0.8-0.1-1.1-0.3
|
||||
c-0.3-0.2-0.4-0.5-0.4-0.9c0-0.2,0-0.4,0.1-0.6c0.1-0.2,0.2-0.3,0.4-0.4c0.2-0.1,0.3-0.2,0.5-0.2c0.1,0,0.4-0.1,0.6-0.1
|
||||
c0.6-0.1,1-0.1,1.3-0.2c0-0.1,0-0.2,0-0.2c0-0.3-0.1-0.5-0.2-0.6c-0.2-0.2-0.5-0.2-0.8-0.2c-0.3,0-0.6,0.1-0.8,0.2
|
||||
c-0.2,0.1-0.3,0.3-0.4,0.6l-0.7-0.1c0.1-0.3,0.2-0.6,0.3-0.7c0.2-0.2,0.4-0.3,0.6-0.4c0.3-0.1,0.6-0.1,1-0.1c0.4,0,0.7,0,0.9,0.1
|
||||
c0.2,0.1,0.4,0.2,0.5,0.3c0.1,0.1,0.2,0.3,0.2,0.5c0,0.1,0,0.4,0,0.7v1c0,0.7,0,1.1,0,1.3c0,0.2,0.1,0.4,0.2,0.5h-0.8
|
||||
C276.7,326.3,276.6,326.1,276.6,325.9z M276.5,324.3c-0.3,0.1-0.7,0.2-1.2,0.3c-0.3,0-0.5,0.1-0.6,0.1c-0.1,0.1-0.2,0.1-0.3,0.2
|
||||
c-0.1,0.1-0.1,0.2-0.1,0.3c0,0.2,0.1,0.4,0.2,0.5c0.1,0.1,0.4,0.2,0.6,0.2c0.3,0,0.5-0.1,0.7-0.2c0.2-0.1,0.4-0.3,0.5-0.5
|
||||
c0.1-0.2,0.1-0.4,0.1-0.7V324.3z M278.5,326.4v-4.3h0.7v0.6c0.3-0.5,0.8-0.7,1.4-0.7c0.3,0,0.5,0,0.7,0.1c0.2,0.1,0.4,0.2,0.5,0.4
|
||||
c0.1,0.1,0.2,0.3,0.2,0.5c0,0.1,0,0.4,0,0.7v2.6h-0.7v-2.6c0-0.3,0-0.5-0.1-0.7c-0.1-0.1-0.2-0.3-0.3-0.3
|
||||
c-0.1-0.1-0.3-0.1-0.5-0.1c-0.3,0-0.6,0.1-0.8,0.3c-0.2,0.2-0.3,0.6-0.3,1.1v2.4H278.5z M283.3,326.4v-4.3h0.7v0.6
|
||||
c0.3-0.5,0.8-0.7,1.4-0.7c0.3,0,0.5,0,0.7,0.1c0.2,0.1,0.4,0.2,0.5,0.4c0.1,0.1,0.2,0.3,0.2,0.5c0,0.1,0,0.4,0,0.7v2.6H286v-2.6
|
||||
c0-0.3,0-0.5-0.1-0.7c-0.1-0.1-0.2-0.3-0.3-0.3c-0.1-0.1-0.3-0.1-0.5-0.1c-0.3,0-0.6,0.1-0.8,0.3c-0.2,0.2-0.3,0.6-0.3,1.1v2.4
|
||||
H283.3z M290.9,325l0.8,0.1c-0.1,0.4-0.3,0.8-0.7,1c-0.3,0.2-0.7,0.4-1.2,0.4c-0.6,0-1.1-0.2-1.5-0.6c-0.4-0.4-0.5-0.9-0.5-1.6
|
||||
c0-0.7,0.2-1.3,0.6-1.7c0.4-0.4,0.9-0.6,1.5-0.6c0.6,0,1,0.2,1.4,0.6c0.4,0.4,0.5,0.9,0.5,1.6c0,0,0,0.1,0,0.2h-3.2
|
||||
c0,0.5,0.2,0.8,0.4,1.1c0.2,0.3,0.5,0.4,0.9,0.4c0.3,0,0.5-0.1,0.7-0.2C290.7,325.6,290.8,325.3,290.9,325z M288.5,323.9h2.4
|
||||
c0-0.4-0.1-0.6-0.3-0.8c-0.2-0.3-0.5-0.4-0.9-0.4c-0.3,0-0.6,0.1-0.8,0.3C288.7,323.2,288.6,323.5,288.5,323.9z M292.7,326.4v-5.9
|
||||
h0.7v5.9H292.7z M297,326.4v-5.9h0.8v5.2h2.9v0.7H297z M301.7,321.3v-0.8h0.7v0.8H301.7z M301.7,326.4v-4.3h0.7v4.3H301.7z
|
||||
M304.2,326.4h-0.7v-5.9h0.7v2.1c0.3-0.4,0.7-0.6,1.2-0.6c0.3,0,0.5,0.1,0.7,0.2c0.2,0.1,0.4,0.3,0.6,0.4c0.2,0.2,0.3,0.4,0.4,0.7
|
||||
c0.1,0.3,0.1,0.6,0.1,0.9c0,0.7-0.2,1.3-0.5,1.7c-0.4,0.4-0.8,0.6-1.3,0.6c-0.5,0-0.9-0.2-1.2-0.6V326.4z M304.2,324.2
|
||||
c0,0.5,0.1,0.9,0.2,1.1c0.2,0.4,0.5,0.6,0.9,0.6c0.3,0,0.6-0.1,0.8-0.4s0.3-0.7,0.3-1.2c0-0.6-0.1-1-0.3-1.2
|
||||
c-0.2-0.3-0.5-0.4-0.8-0.4s-0.6,0.1-0.8,0.4C304.3,323.3,304.2,323.7,304.2,324.2z M308.3,326.4v-4.3h0.7v0.6
|
||||
c0.2-0.3,0.3-0.5,0.5-0.6c0.1-0.1,0.3-0.1,0.5-0.1c0.2,0,0.5,0.1,0.7,0.2l-0.2,0.7c-0.2-0.1-0.4-0.2-0.5-0.2c-0.2,0-0.3,0-0.4,0.1
|
||||
c-0.1,0.1-0.2,0.2-0.3,0.4c-0.1,0.3-0.1,0.5-0.1,0.8v2.3H308.3z M313.9,325.9c-0.3,0.2-0.5,0.4-0.8,0.5c-0.2,0.1-0.5,0.1-0.8,0.1
|
||||
c-0.5,0-0.8-0.1-1.1-0.3s-0.4-0.5-0.4-0.9c0-0.2,0-0.4,0.1-0.6c0.1-0.2,0.2-0.3,0.4-0.4c0.2-0.1,0.3-0.2,0.5-0.2
|
||||
c0.1,0,0.4-0.1,0.6-0.1c0.6-0.1,1-0.1,1.3-0.2c0-0.1,0-0.2,0-0.2c0-0.3-0.1-0.5-0.2-0.6c-0.2-0.2-0.5-0.2-0.8-0.2
|
||||
c-0.3,0-0.6,0.1-0.8,0.2c-0.2,0.1-0.3,0.3-0.4,0.6l-0.7-0.1c0.1-0.3,0.2-0.6,0.3-0.7c0.2-0.2,0.4-0.3,0.6-0.4
|
||||
c0.3-0.1,0.6-0.1,1-0.1c0.4,0,0.7,0,0.9,0.1c0.2,0.1,0.4,0.2,0.5,0.3c0.1,0.1,0.2,0.3,0.2,0.5c0,0.1,0,0.4,0,0.7v1
|
||||
c0,0.7,0,1.1,0,1.3s0.1,0.4,0.2,0.5h-0.8C314,326.3,314,326.1,313.9,325.9z M313.9,324.3c-0.3,0.1-0.7,0.2-1.2,0.3
|
||||
c-0.3,0-0.5,0.1-0.6,0.1s-0.2,0.1-0.3,0.2c-0.1,0.1-0.1,0.2-0.1,0.3c0,0.2,0.1,0.4,0.2,0.5c0.1,0.1,0.4,0.2,0.6,0.2
|
||||
c0.3,0,0.5-0.1,0.7-0.2c0.2-0.1,0.4-0.3,0.5-0.5c0.1-0.2,0.1-0.4,0.1-0.7V324.3z M315.8,326.4v-4.3h0.7v0.6
|
||||
c0.2-0.3,0.3-0.5,0.5-0.6c0.1-0.1,0.3-0.1,0.5-0.1c0.2,0,0.5,0.1,0.7,0.2l-0.2,0.7c-0.2-0.1-0.4-0.2-0.5-0.2c-0.2,0-0.3,0-0.4,0.1
|
||||
c-0.1,0.1-0.2,0.2-0.3,0.4c-0.1,0.3-0.1,0.5-0.1,0.8v2.3H315.8z M318.7,328.1l-0.1-0.7c0.2,0,0.3,0.1,0.4,0.1c0.2,0,0.3,0,0.4-0.1
|
||||
c0.1-0.1,0.2-0.1,0.2-0.2c0-0.1,0.1-0.3,0.2-0.5c0,0,0-0.1,0.1-0.2l-1.6-4.3h0.8l0.9,2.5c0.1,0.3,0.2,0.7,0.3,1
|
||||
c0.1-0.3,0.2-0.7,0.3-1l0.9-2.5h0.7l-1.6,4.4c-0.2,0.5-0.3,0.8-0.4,1c-0.1,0.2-0.3,0.4-0.4,0.5s-0.4,0.2-0.6,0.2
|
||||
C319,328.2,318.8,328.1,318.7,328.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 70 KiB |
BIN
images/arrow-down.png
Normal file
BIN
images/arrow-down.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 423 B |
4
images/dataflow.svg
Normal file
4
images/dataflow.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 85 KiB |
BIN
images/forum.png
Normal file
BIN
images/forum.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 275 B |
BIN
images/octocat-small.png
Normal file
BIN
images/octocat-small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 570 B |
19
include.yaml
19
include.yaml
@@ -1,19 +0,0 @@
|
||||
handlers:
|
||||
- url: /cosmopolite/api
|
||||
script: cosmopolite.api.app
|
||||
secure: always
|
||||
|
||||
- url: /cosmopolite/auth/.*
|
||||
script: cosmopolite.auth.app
|
||||
secure: always
|
||||
|
||||
- url: /_ah/channel/.*
|
||||
script: cosmopolite.channel.app
|
||||
|
||||
- url: /cosmopolite/static
|
||||
static_dir: cosmopolite/static
|
||||
secure: always
|
||||
http_headers:
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
||||
83
index.html
Normal file
83
index.html
Normal file
@@ -0,0 +1,83 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||
<title>Cosmopolite</title>
|
||||
|
||||
<link rel="stylesheet" href="stylesheets/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<h1 class="header">Cosmopolite</h1>
|
||||
<p class="header">Client/server publish/subscribe, presence and key/value storage for AppEngine</p>
|
||||
|
||||
<ul>
|
||||
<li class="download"><a class="buttons" href="https://github.com/flamingcowtv/cosmopolite/zipball/master" onclick="return trackOutboundLink(this.href);">Download ZIP</a></li>
|
||||
<li class="download"><a class="buttons" href="https://github.com/flamingcowtv/cosmopolite/tarball/master" onclick="return trackOutboundLink(this.href);">Download TAR</a></li>
|
||||
<li><a class="buttons github" href="https://github.com/flamingcowtv/cosmopolite" onclick="return trackOutboundLink(this.href);">View On GitHub</a></li>
|
||||
<li><a class="buttons forum" href="https://groups.google.com/a/cosmopolite.org/forum/#!forum/discuss" onclick="return trackOutboundLink(this.href);">Mailing list</a></li>
|
||||
</ul>
|
||||
|
||||
</header>
|
||||
<section>
|
||||
<h1>
|
||||
<a name="cosmopolite" class="anchor" href="#cosmopolite"><span class="octicon octicon-link"></span></a>Cosmopolite</h1>
|
||||
|
||||
<p>Client/server publish/subscribe, presence and key/value storage for <a href="https://developers.google.com/appengine/">AppEngine</a>.</p>
|
||||
|
||||
<h3>Dig in</h3>
|
||||
|
||||
<ul>
|
||||
<li style="font-weight: bold;">Try out <a href="https://playground.cosmopolite.org/">the playground</a></li>
|
||||
<li style="font-weight: bold;">Read <a href="overview">the overview</a></li>
|
||||
<li style="font-weight: bold;">Read <a href="tutorial">the tutorial</a></li>
|
||||
<li style="font-weight: bold;">Read <a href="reference">the reference</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Components</h3>
|
||||
|
||||
<ul>
|
||||
<li>A server API built on the <a href="https://developers.google.com/appengine/docs/python/">AppEngine Python framework</a>.</li>
|
||||
<li>A browser client library written in JavaScript.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Feature overview</h3>
|
||||
|
||||
<ul>
|
||||
<li>Near-realtime notification to subscribers of messages published to a "subject"</li>
|
||||
<li>Server-side storage of past messages for replay later to clients</li>
|
||||
<li>Ephemeral messages tied to client connection that can be used for presence information</li>
|
||||
<li>Client identification persistence via <a href="http://www.w3schools.com/html/html5_webstorage.asp">localStorage</a> tokens or in combination with <a href="https://developers.google.com/appengine/docs/python/users/">Google account signin</a></li>
|
||||
<li>Complex messages supported via transparent <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON">JSON</a> serialization</li>
|
||||
<li>Server-side strict ordering of messages</li>
|
||||
<li>Client-side message queueing in localStorage and resumption on restart</li>
|
||||
<li>Message duplication detection and elimination</li>
|
||||
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a> support for notification of client -> server operation completion</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-37845853-2', 'auto');
|
||||
ga('require', 'displayfeatures');
|
||||
ga('send', 'pageview');
|
||||
|
||||
var trackOutboundLink = function(url) {
|
||||
ga('send', 'event', 'outbound', 'click', url, {
|
||||
'hitCallback': function () {
|
||||
document.location = url;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
69
lib/auth.py
69
lib/auth.py
@@ -1,69 +0,0 @@
|
||||
# Copyright 2014, Ian Gulliver
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import random
|
||||
import string
|
||||
|
||||
from google.appengine.api import memcache
|
||||
from google.appengine.ext import db
|
||||
|
||||
|
||||
class BadSignature(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AuthKey(db.Model):
|
||||
auth_key = db.ByteStringProperty(required=True)
|
||||
live = db.BooleanProperty(required=True, default=True)
|
||||
|
||||
|
||||
_KEY_CHARS = string.ascii_letters + string.digits
|
||||
_KEY_LENGTH = 64
|
||||
|
||||
def GetAuthKey():
|
||||
auth_key = memcache.get('auth_key')
|
||||
if auth_key:
|
||||
return auth_key
|
||||
|
||||
for key in AuthKey.all().filter('live =', True):
|
||||
auth_key = key.auth_key
|
||||
|
||||
if not auth_key:
|
||||
auth_key = ''.join(random.choice(_KEY_CHARS) for _ in xrange(_KEY_LENGTH))
|
||||
AuthKey(auth_key=auth_key).put()
|
||||
|
||||
memcache.set('auth_key', auth_key)
|
||||
return auth_key
|
||||
|
||||
|
||||
def Sign(value):
|
||||
sig = hmac.new(GetAuthKey(), str(value), hashlib.sha512)
|
||||
return '%s:%s' % (value, sig.hexdigest())
|
||||
|
||||
|
||||
def Parse(token):
|
||||
if not token:
|
||||
return None
|
||||
value, sig_digest = token.split(':', 1)
|
||||
if token != Sign(value):
|
||||
raise BadSignature
|
||||
return value
|
||||
|
||||
|
||||
def ParseKey(token):
|
||||
if not token:
|
||||
return None
|
||||
return db.Key(encoded=Parse(token))
|
||||
288
lib/models.py
288
lib/models.py
@@ -1,288 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2014, Ian Gulliver
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from google.appengine.api import channel
|
||||
from google.appengine.ext import db
|
||||
|
||||
import utils
|
||||
|
||||
|
||||
# Profile
|
||||
# ↳ Client
|
||||
#
|
||||
# Subject
|
||||
# ↳ Message
|
||||
# ↳ Subscription (⤴︎ Client)
|
||||
|
||||
|
||||
class DuplicateMessage(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AccessDenied(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Profile(db.Model):
|
||||
google_user = db.UserProperty()
|
||||
|
||||
@classmethod
|
||||
def FromGoogleUser(cls, google_user):
|
||||
if not google_user:
|
||||
profile = Profile()
|
||||
profile.put()
|
||||
return profile
|
||||
|
||||
profiles = Profile.all().filter('google_user =', google_user).fetch(1)
|
||||
if profiles:
|
||||
return profiles[0]
|
||||
else:
|
||||
# TODO(flamingcow): Fetch-then-store uniqueness is a race.
|
||||
profile = Profile(google_user=google_user)
|
||||
profile.put()
|
||||
return profile
|
||||
|
||||
def MergeFrom(self, source_profile):
|
||||
# This is non-transactional and racy (new messages can be introduced by the
|
||||
# old client after we start). This is hard to solve because a) we're not in
|
||||
# a single hierarchy and b) we don't revoke the old client ID, so it can
|
||||
# still be used.
|
||||
for message in Message.all().filter('sender =', source_profile):
|
||||
message.sender = self;
|
||||
message.put()
|
||||
|
||||
|
||||
class Client(db.Model):
|
||||
# parent=Profile
|
||||
|
||||
first_seen = db.DateTimeProperty(required=True, auto_now_add=True)
|
||||
channel_active = db.BooleanProperty(required=True, default=False)
|
||||
|
||||
@classmethod
|
||||
def FromProfile(cls, profile):
|
||||
client = cls(parent=profile)
|
||||
client.put()
|
||||
return client
|
||||
|
||||
@classmethod
|
||||
def FromGoogleUser(cls, google_user):
|
||||
profile = Profile.FromGoogleUser(google_user)
|
||||
return cls.FromProfile(profile)
|
||||
|
||||
def SendMessage(self, msg):
|
||||
self.SendByKey(self.key(), msg)
|
||||
|
||||
@staticmethod
|
||||
def SendByKey(key, msg):
|
||||
channel.send_message(str(key), json.dumps(msg, default=utils.EncodeJSON))
|
||||
|
||||
|
||||
class Subject(db.Model):
|
||||
|
||||
name = db.StringProperty(required=True)
|
||||
readable_only_by = db.ReferenceProperty(
|
||||
reference_class=Profile, collection_name='readable_subject_set')
|
||||
writable_only_by = db.ReferenceProperty(
|
||||
reference_class=Profile, collection_name='writable_subject_set')
|
||||
|
||||
next_message_id = db.IntegerProperty(required=True, default=1)
|
||||
|
||||
@classmethod
|
||||
def FindOrCreate(cls, subject):
|
||||
if 'readable_only_by' in subject:
|
||||
readable_only_by = Profile.get(subject['readable_only_by'])
|
||||
else:
|
||||
readable_only_by = None
|
||||
|
||||
if 'writable_only_by' in subject:
|
||||
writable_only_by = Profile.get(subject['writable_only_by'])
|
||||
else:
|
||||
writable_only_by = None
|
||||
|
||||
subjects = (
|
||||
cls.all()
|
||||
.filter('name =', subject['name'])
|
||||
.filter('readable_only_by =', readable_only_by)
|
||||
.filter('writable_only_by =', writable_only_by)
|
||||
.fetch(1))
|
||||
if subjects:
|
||||
return subjects[0]
|
||||
subject = cls(
|
||||
name=subject['name'],
|
||||
readable_only_by=readable_only_by,
|
||||
writable_only_by=writable_only_by)
|
||||
subject.put()
|
||||
return subject
|
||||
|
||||
@db.transactional()
|
||||
def GetRecentMessages(self, num_messages):
|
||||
query = (
|
||||
Message.all()
|
||||
.ancestor(self)
|
||||
.order('-id_'))
|
||||
if num_messages <= 0:
|
||||
num_messages = None
|
||||
return reversed(query.fetch(limit=num_messages))
|
||||
|
||||
@db.transactional()
|
||||
def GetMessagesSince(self, last_id):
|
||||
query = (
|
||||
Message.all()
|
||||
.ancestor(self)
|
||||
.filter('id_ >', last_id)
|
||||
.order('id_'))
|
||||
return list(query)
|
||||
|
||||
@db.transactional()
|
||||
def GetKey(self, key):
|
||||
messages = (
|
||||
Message.all()
|
||||
.ancestor(self)
|
||||
.filter('key_ =', key)
|
||||
.order('-id_')
|
||||
.fetch(1))
|
||||
if messages:
|
||||
return messages[0]
|
||||
return None
|
||||
|
||||
@db.transactional()
|
||||
def PutMessage(self, message, sender, sender_message_id, key=None):
|
||||
"""Internal helper for SendMessage().
|
||||
|
||||
Unless/until channel.send_message becomes transactional, we have to finish
|
||||
the datastore work (and any retries) before we start transmitting to
|
||||
channels.
|
||||
"""
|
||||
# We have to reload the Subject inside the transaction to get transactional
|
||||
# ID generation
|
||||
subject = Subject.get(self.key())
|
||||
|
||||
# sender_message_id should be universal across all subjects, but we check
|
||||
# it within just this subject to allow in-transaction verification.
|
||||
messages = (
|
||||
Message.all()
|
||||
.ancestor(subject)
|
||||
.filter('sender_message_id =', sender_message_id)
|
||||
.fetch(1))
|
||||
if messages:
|
||||
raise DuplicateMessage(sender_message_id)
|
||||
|
||||
message_id = subject.next_message_id
|
||||
subject.next_message_id += 1
|
||||
subject.put()
|
||||
|
||||
obj = Message(
|
||||
parent=subject,
|
||||
message=message,
|
||||
sender=sender,
|
||||
sender_message_id=sender_message_id,
|
||||
id_=message_id,
|
||||
key_=key)
|
||||
obj.put()
|
||||
|
||||
return (
|
||||
obj,
|
||||
[Subscription.client.get_value_for_datastore(subscription)
|
||||
for subscription in Subscription.all().ancestor(subject)])
|
||||
|
||||
def SendMessage(self, message, sender, sender_message_id, key=None):
|
||||
writable_only_by = Subject.writable_only_by.get_value_for_datastore(self)
|
||||
if (writable_only_by and
|
||||
writable_only_by != sender):
|
||||
raise AccessDenied
|
||||
obj, subscriptions = self.PutMessage(message, sender, sender_message_id, key)
|
||||
event = obj.ToEvent()
|
||||
for subscription in subscriptions:
|
||||
Client.SendByKey(subscription, event)
|
||||
|
||||
def ToDict(self):
|
||||
ret = {
|
||||
'name': self.name,
|
||||
}
|
||||
readable_only_by = Subject.readable_only_by.get_value_for_datastore(self)
|
||||
if readable_only_by:
|
||||
ret['readable_only_by'] = readable_only_by
|
||||
writable_only_by = Subject.writable_only_by.get_value_for_datastore(self)
|
||||
if writable_only_by:
|
||||
ret['writable_only_by'] = writable_only_by
|
||||
return ret
|
||||
|
||||
|
||||
class Subscription(db.Model):
|
||||
# parent=Subject
|
||||
|
||||
client = db.ReferenceProperty(reference_class=Client)
|
||||
|
||||
@classmethod
|
||||
@db.transactional()
|
||||
def FindOrCreate(cls, subject, client, messages=0, last_id=None):
|
||||
readable_only_by = (
|
||||
Subject.readable_only_by.get_value_for_datastore(subject))
|
||||
if (readable_only_by and
|
||||
readable_only_by != client.parent_key()):
|
||||
raise AccessDenied
|
||||
|
||||
subscriptions = (
|
||||
cls.all(keys_only=True)
|
||||
.ancestor(subject)
|
||||
.filter('client =', client)
|
||||
.fetch(1))
|
||||
if not subscriptions:
|
||||
cls(parent=subject, client=client).put()
|
||||
events = []
|
||||
if messages:
|
||||
events.extend(m.ToEvent() for m in subject.GetRecentMessages(messages))
|
||||
if last_id is not None:
|
||||
events.extend(m.ToEvent() for m in subject.GetMessagesSince(last_id))
|
||||
return events
|
||||
|
||||
@classmethod
|
||||
@db.transactional()
|
||||
def Remove(cls, subject, client):
|
||||
subscriptions = (
|
||||
cls.all()
|
||||
.ancestor(subject)
|
||||
.filter('client =', client))
|
||||
for subscription in subscriptions:
|
||||
subscription.delete()
|
||||
|
||||
|
||||
class Message(db.Model):
|
||||
# parent=Subject
|
||||
|
||||
created = db.DateTimeProperty(required=True, auto_now_add=True)
|
||||
message = db.TextProperty(required=True)
|
||||
sender = db.ReferenceProperty(required=True, reference_class=Profile)
|
||||
sender_message_id = db.StringProperty(required=True)
|
||||
# id is reserved
|
||||
id_ = db.IntegerProperty(required=True)
|
||||
# key and key_name are reserved
|
||||
key_ = db.StringProperty()
|
||||
|
||||
def ToEvent(self):
|
||||
ret = {
|
||||
'event_type': 'message',
|
||||
'id': self.id_,
|
||||
'sender': str(Message.sender.get_value_for_datastore(self)),
|
||||
'subject': self.parent().ToDict(),
|
||||
'created': self.created,
|
||||
'message': self.message,
|
||||
}
|
||||
if self.key_:
|
||||
ret['key'] = self.key_
|
||||
return ret
|
||||
@@ -1,70 +0,0 @@
|
||||
# Copyright 2014, Ian Gulliver
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import functools
|
||||
|
||||
from google.appengine.api import users
|
||||
|
||||
from cosmopolite.lib import auth
|
||||
|
||||
|
||||
def google_user_xsrf_protection(handler):
|
||||
"""Validate google user cookie.
|
||||
|
||||
We can't trust that the action being requested is being made by this user due
|
||||
to XSRF concerns (since google user is stored in a cookie). We have to make
|
||||
sure that this user can actually receive responses, so we ask them to pass a
|
||||
second token about their user that we can validate.
|
||||
"""
|
||||
|
||||
@functools.wraps(handler)
|
||||
def ValidateGoogleUser(self):
|
||||
self.verified_google_user = None
|
||||
|
||||
google_user = users.get_current_user()
|
||||
if not google_user:
|
||||
return handler(self)
|
||||
|
||||
google_user_id = auth.Parse(self.request_json.get('google_user_id', None))
|
||||
if (not google_user_id or
|
||||
google_user_id != google_user.user_id()):
|
||||
return {
|
||||
'status': 'retry',
|
||||
'google_user_id': auth.Sign(google_user.user_id()),
|
||||
}
|
||||
|
||||
self.verified_google_user = google_user
|
||||
return handler(self)
|
||||
|
||||
return ValidateGoogleUser
|
||||
|
||||
|
||||
def weak_security_checks(handler):
|
||||
|
||||
@functools.wraps(handler)
|
||||
def CheckOriginHeader(self):
|
||||
origin = self.request.headers.get('Origin')
|
||||
if origin:
|
||||
host = self.request.headers.get('Host')
|
||||
possible_origins = {
|
||||
'http://%s' % host,
|
||||
'https://%s' % host,
|
||||
}
|
||||
if origin not in possible_origins:
|
||||
self.error(403)
|
||||
self.response.out.write('Origin/Host header mismatch')
|
||||
return
|
||||
return handler(self)
|
||||
|
||||
return CheckOriginHeader
|
||||
@@ -1,85 +0,0 @@
|
||||
# Copyright 2014, Ian Gulliver
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import functools
|
||||
|
||||
from google.appengine.api import users
|
||||
|
||||
from cosmopolite.lib import auth
|
||||
from cosmopolite.lib import models
|
||||
|
||||
|
||||
def _CheckClientAndGoogleUser(client, google_user):
|
||||
if not google_user:
|
||||
# Nothing to check. If there's a user on the profile, it can stay there.
|
||||
return client
|
||||
|
||||
client_profile_google_user = client.parent().google_user
|
||||
if client_profile_google_user:
|
||||
if client_profile_google_user == google_user:
|
||||
return client
|
||||
else:
|
||||
# Shared computer? Google account wins.
|
||||
return models.Client.FromGoogleUser(google_user)
|
||||
|
||||
# User just signed in. Their anonymous profile gets permanently
|
||||
# associated with this Google account.
|
||||
profiles = (models.Profile.all()
|
||||
.filter('google_user =', google_user)
|
||||
.fetch(1))
|
||||
if profiles:
|
||||
# We can't convert the anonymous profile because there's already
|
||||
# a profile for this Google user. Create a new client_id pointing to that
|
||||
# profile.
|
||||
# TODO(flamingcow): Fetch-then-store uniqueness is a race.
|
||||
google_profile = profiles[0]
|
||||
google_profile.MergeFrom(client.parent_key())
|
||||
return models.Client.FromProfile(google_profile)
|
||||
|
||||
# First time signin.
|
||||
client_profile = client.parent()
|
||||
client_profile.google_user = google_user
|
||||
client_profile.put()
|
||||
return client
|
||||
|
||||
|
||||
def session_required(handler):
|
||||
"""Find or create a session for this user.
|
||||
|
||||
Find or create a Client and Profile for this user. Muck with the return value
|
||||
to wrap it in an object that contains session info for the client.
|
||||
|
||||
Make sure to wrap this in google_user_xsrf_protection.
|
||||
"""
|
||||
|
||||
@functools.wraps(handler)
|
||||
def FindOrCreateSession(self):
|
||||
client_key = auth.ParseKey(self.request_json.get('client_id', None))
|
||||
|
||||
# The hunt for a Profile begins.
|
||||
if client_key:
|
||||
self.client = _CheckClientAndGoogleUser(
|
||||
models.Client.get(client_key),
|
||||
self.verified_google_user)
|
||||
else:
|
||||
self.client = models.Client.FromGoogleUser(self.verified_google_user)
|
||||
|
||||
ret = handler(self)
|
||||
if client_key != self.client.key():
|
||||
# Tell the client that this changed
|
||||
ret['client_id'] = auth.Sign(self.client.key())
|
||||
|
||||
return ret
|
||||
|
||||
return FindOrCreateSession
|
||||
74
lib/utils.py
74
lib/utils.py
@@ -1,74 +0,0 @@
|
||||
# Copyright 2014, Ian Gulliver
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import functools
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
|
||||
from google.appengine.api import namespace_manager
|
||||
|
||||
from cosmopolite import config
|
||||
|
||||
from cosmopolite.lib import auth
|
||||
|
||||
|
||||
def expects_json(handler):
|
||||
|
||||
@functools.wraps(handler)
|
||||
def ParseInput(self):
|
||||
self.request_json = json.load(self.request.body_file)
|
||||
return handler(self)
|
||||
|
||||
return ParseInput
|
||||
|
||||
|
||||
def returns_json(handler):
|
||||
|
||||
@functools.wraps(handler)
|
||||
def SerializeResult(self):
|
||||
json.dump(handler(self), self.response.out, default=EncodeJSON)
|
||||
|
||||
return SerializeResult
|
||||
|
||||
|
||||
def chaos_monkey(handler):
|
||||
|
||||
@functools.wraps(handler)
|
||||
def IntroduceFailures(self):
|
||||
if random.random() < config.CHAOS_PROBABILITY:
|
||||
self.response.headers['Retry-After'] = '0'
|
||||
self.error(503)
|
||||
return
|
||||
return handler(self)
|
||||
|
||||
return IntroduceFailures
|
||||
|
||||
|
||||
def local_namespace(handler):
|
||||
|
||||
@functools.wraps(handler)
|
||||
def SetNamespace(self):
|
||||
import logging
|
||||
namespace_manager.set_namespace(config.NAMESPACE)
|
||||
return handler(self)
|
||||
|
||||
return SetNamespace
|
||||
|
||||
|
||||
def EncodeJSON(o):
|
||||
if isinstance(o, datetime.datetime):
|
||||
return time.mktime(o.timetuple())
|
||||
return json.JSONEncoder.default(o)
|
||||
60
overview.html
Normal file
60
overview.html
Normal file
@@ -0,0 +1,60 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||
<title>Cosmopolite Overview</title>
|
||||
|
||||
<link rel="stylesheet" href="stylesheets/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<h1 class="header"><a href="/">Cosmopolite</a></h1>
|
||||
<p class="header">Client/server publish/subscribe, presence and key/value storage for AppEngine</p>
|
||||
|
||||
<ul>
|
||||
<li class="download"><a class="buttons" href="https://github.com/flamingcowtv/cosmopolite/zipball/master" onclick="return trackOutboundLink(this.href);">Download ZIP</a></li>
|
||||
<li class="download"><a class="buttons" href="https://github.com/flamingcowtv/cosmopolite/tarball/master" onclick="return trackOutboundLink(this.href);">Download TAR</a></li>
|
||||
<li><a class="buttons github" href="https://github.com/flamingcowtv/cosmopolite" onclick="return trackOutboundLink(this.href);">View On GitHub</a></li>
|
||||
<li><a class="buttons forum" href="https://groups.google.com/a/cosmopolite.org/forum/#!forum/discuss" onclick="return trackOutboundLink(this.href);">Mailing list</a></li>
|
||||
</ul>
|
||||
|
||||
</header>
|
||||
<section>
|
||||
<h1>
|
||||
<a name="cosmopolite" class="anchor" href="#cosmopolite"><span class="octicon octicon-link"></span></a>Cosmopolite Ovewview</h1>
|
||||
|
||||
<h3><a name="architecture">Architecture</a></h3>
|
||||
|
||||
<img src="images/architecture.svg">
|
||||
|
||||
<h3><a name="dataflow">Data flow</a></h3>
|
||||
|
||||
<img src="images/dataflow.svg">
|
||||
|
||||
</section>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-37845853-2', 'auto');
|
||||
ga('require', 'displayfeatures');
|
||||
ga('send', 'pageview');
|
||||
|
||||
var trackOutboundLink = function(url) {
|
||||
ga('send', 'event', 'outbound', 'click', url, {
|
||||
'hitCallback': function () {
|
||||
document.location = url;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
694
reference.html
Normal file
694
reference.html
Normal file
@@ -0,0 +1,694 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||
<title>Cosmopolite Reference</title>
|
||||
|
||||
<link rel="stylesheet" href="stylesheets/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<h1 class="header"><a href="/">Cosmopolite</a></h1>
|
||||
<p class="header">Client/server publish/subscribe, presence and key/value storage for AppEngine</p>
|
||||
|
||||
<ul>
|
||||
<li class="download"><a class="buttons" href="https://github.com/flamingcowtv/cosmopolite/zipball/master" onclick="return trackOutboundLink(this.href);">Download ZIP</a></li>
|
||||
<li class="download"><a class="buttons" href="https://github.com/flamingcowtv/cosmopolite/tarball/master" onclick="return trackOutboundLink(this.href);">Download TAR</a></li>
|
||||
<li><a class="buttons github" href="https://github.com/flamingcowtv/cosmopolite" onclick="return trackOutboundLink(this.href);">View On GitHub</a></li>
|
||||
<li><a class="buttons forum" href="https://groups.google.com/a/cosmopolite.org/forum/#!forum/discuss" onclick="return trackOutboundLink(this.href);">Mailing list</a></li>
|
||||
</ul>
|
||||
|
||||
</header>
|
||||
<section>
|
||||
<h1>
|
||||
<a name="cosmopolite" class="anchor" href="#cosmopolite"><span class="octicon octicon-link"></span></a>Cosmopolite Reference</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="#terms">Terms</a>
|
||||
<ul>
|
||||
<li><a href="#profile">Profile</a></li>
|
||||
<li><a href="#client">Client</a></li>
|
||||
<li><a href="#instance">Instance</a></li>
|
||||
<li><a href="#channel">Channel</a></li>
|
||||
<li><a href="#subject">Subject</a></li>
|
||||
<li><a href="#subscription">Subscription</a></li>
|
||||
<li><a href="#message">Message</a></li>
|
||||
<li><a href="#pin">Pin</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><a href="#events">Events</a>
|
||||
<ul>
|
||||
<li><a href="#connect">connect</a></li>
|
||||
<li><a href="#disconnect">disconnect</a></li>
|
||||
<li><a href="#login">login</a></li>
|
||||
<li><a href="#logout">logout</a></li>
|
||||
<li><a href="#message_event">message</a></li>
|
||||
<li><a href="#pin_event">pin</a></li>
|
||||
<li><a href="#unpin_event">unpin</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li><a href="#methods">Methods</a>
|
||||
<ul>
|
||||
<li><a href="#constructor">Constructor</a></li>
|
||||
<li><a href="#shutdown">shutdown()</a></li>
|
||||
<li><a href="#connected">connected()</a></li>
|
||||
<li><a href="#getProfile">getProfile()</a></li>
|
||||
<li><a href="#currentProfile">currentProfile()</a></li>
|
||||
<li><a href="#subscribe">subscribe()</a></li>
|
||||
<li><a href="#unsubscribe">unsubscribe()</a></li>
|
||||
<li><a href="#sendMessage">sendMessage()</a></li>
|
||||
<li><a href="#getMessages">getMessages()</a></li>
|
||||
<li><a href="#getLastMessage">getLastMessage()</a></li>
|
||||
<li><a href="#pin_method">pin()</a></li>
|
||||
<li><a href="#unpin">unpin()</a></li>
|
||||
<li><a href="#getPins">getPins()</a></li>
|
||||
<li><a href="#trackEvent">trackEvent()</a></li>
|
||||
<li><a href="#uuid">uuid()</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3><a name="terms">Terms</a></h3>
|
||||
|
||||
<h4><a name="profile">Profile</a></h4>
|
||||
|
||||
<p>A profile is a collection of clients that all speak as the same user, and so should appear as the
|
||||
same to subscribers. Profiles are identified by a string key assigned by the server. Clients can
|
||||
fetch their own profile by calling <a href="#getProfile">getProfile()</a> or
|
||||
<a href="#currentProfile">currentProfile()</a>. Profile information for other users is provided
|
||||
in the <i>sender</i> field of a <a href="#message">Message</a>.</p>
|
||||
|
||||
<p>A client keeps its profile when it logs in to a Google account, accept if that account is
|
||||
already associated with a different profile. In that case, messages that were sent using the old
|
||||
profile are re-assigned to the profile associated with the logged in user.</p>
|
||||
|
||||
<p>Logging out changes profiles.</p>
|
||||
|
||||
<p>The profile key is passed to Google Analytics (if enabled) as the userId, so sessions from
|
||||
different clients across different browsers are associated with the same user for analytics
|
||||
purposes.</p>
|
||||
|
||||
|
||||
<h4><a name="client">Client</a></h4>
|
||||
|
||||
<p>A client is a persistent identifier for a given browser. It is identified by a string key
|
||||
generated by the client and stored in localStorage, so it is persistent across restarts (except in
|
||||
incognito mode or the like). A client is associated with exactly one <a href="#profile">profile</a>
|
||||
at any time, but may change associated profiles on log in.</p>
|
||||
|
||||
<p>Logging out generates and stores a new client key.</p>
|
||||
|
||||
|
||||
<h4><a name="instance">Instance</a></h4>
|
||||
|
||||
<p>An instance is a persistent identifier for a single connection to the server. It is generated
|
||||
when a new Cosmopolite instance is constructed or whenever the server -> client
|
||||
<a href="#channel">channel</a> connection is interrupted.</p>
|
||||
|
||||
<p>Instances are used to track <a href="#subscription">Subscriptions</a> and <a href="#pin">Pins</a>
|
||||
which cease to exist (from the server perspective) when a client disconnects.</p>
|
||||
|
||||
|
||||
<h4><a name="channel">Channel</a></h4>
|
||||
|
||||
<p>A channel is the message push connection from the server to the client used to send updates. It
|
||||
uses Google AppEngine's
|
||||
<a href="https://developers.google.com/appengine/docs/python/channel/">Channel service</a>.</p>
|
||||
|
||||
<p>In addition to message passing, the channel library also provides notification of connection
|
||||
state to the server. This is passed to the application code using the
|
||||
<a href="#connect">connect</a> and <a href="#disconnect">disconnect</a> events.</p>
|
||||
|
||||
|
||||
<h4><a name="subject">Subject</a></h4>
|
||||
|
||||
<p>A subject is a topic that clients publish and subscribe to. Subjects may be strings, numbers
|
||||
or objects with access control information embedded. The follow are all valid <b>and different</b>
|
||||
subjects:
|
||||
|
||||
<ul>
|
||||
<li><code>"foo"</code></li>
|
||||
<li><code>5</code></li>
|
||||
<li><code>{ name: "foo", readable_only_by: "XXXprofileXXX" }</code></li>
|
||||
<li><code>{ name: "foo", writable_only_by: "YYYprofileYYY" }</code></li>
|
||||
<li><code>{ name: "foo", readable_only_by: "XXXprofileXXX", writable_only_by: "YYYprofileYYY" }</code></li>
|
||||
<li><code>{ name: "foo", readable_only_by: "admin" }</code></li>
|
||||
<li><code>{ name: "foo", writable_only_by: "admin" }</code></li>
|
||||
<li><code>{ name: "foo", readable_only_by: "me" }</code></li>
|
||||
<li><code>{ name: "foo", writable_only_by: "me" }</code></li>
|
||||
</ul>
|
||||
|
||||
where XXXprofileXXX and YYYprofileYYY are profile keys. <code>admin</code> is a magic value that
|
||||
only allows accounts marked as administrators in the AppEngine application to perform the action.
|
||||
<code>me</code> is s placeholder that is replaced on the server side with the current profile ID
|
||||
in both requests and responses. Using the result of <a href="#currentProfile">currentProfile()</a>
|
||||
instead of <code>me</code> will fetch the same data from the server but appear as a different
|
||||
group in the client.
|
||||
</p>
|
||||
|
||||
|
||||
<h4><a name="subscription">Subscription</a></h4>
|
||||
|
||||
<p>A subscription ties an <a href="#instance">instance</a> to a <a href="#subject">subject</a> and
|
||||
requests that the client be kept informed of changes to that subject. Changes that cause
|
||||
notification are:
|
||||
|
||||
<ul>
|
||||
<li>A client sends a <a href="#message">message</a> to the subject.</li>
|
||||
<li>A client adds a <a href="#pin">pin</a> to the subject.</li>
|
||||
<li>A client removes a <a href="#pin">pin</a> from the subject.</li>
|
||||
<li>A client which had previously added a <a href="#pin">pin</a> disconnects. This appears to an
|
||||
observer as if the client had removed the pin.</li>
|
||||
</ul>
|
||||
|
||||
When a new subscription is established, the server sends all active pins and a client-specified
|
||||
subset of historical messages to the client.</p>
|
||||
|
||||
<p>Subscriptions are automatically destroyed on the server side when an instance disconnects. The
|
||||
client library keeps lists of subjects and the last messages seen from them and automatically
|
||||
re-subscribes after reconnection. This process is transparent to application code.</p>
|
||||
|
||||
<p>Subscriptions are created by the application using the <a href="#subscribe">subscribe()</a>
|
||||
method.</p>
|
||||
|
||||
|
||||
<h4><a name="message">Message</a></h4>
|
||||
|
||||
<p>A message is a piece of information sent by a <a href="#client">client</a> to a
|
||||
<a href="#subject">subject</a>. It is stored by the server and transmitted to any clients currently
|
||||
<a href="#subscription">subscribing</a> to that subject.</p>
|
||||
|
||||
<p>A message can consist of any type that is serializable in JSON: numbers, booleans, strings,
|
||||
null, arrays and objects composed of these values (or of other arrays and objects). Serialization
|
||||
and deserialization is handled by Cosmopolite.</p>
|
||||
|
||||
<p>When a message is received (via a <a href="#message_event">message</a> event or the return
|
||||
result from <a href="#getMessages">getMessages()</a> or
|
||||
<a href="#getLastMessage">getLastMessage()</a>), metadata is attached, packaged into an object with
|
||||
the message data itself:
|
||||
|
||||
<pre>{
|
||||
<comment>// Server-assigned sequence number</comment>
|
||||
"id": 1,
|
||||
|
||||
<comment>// Type may also be <a href="#pin">"pin"</a></comment>
|
||||
"event_type": "message",
|
||||
|
||||
<comment>// Sender-assigned UUID</comment>
|
||||
"sender_message_id": "cbdae48d-7a02-4850-208c-986b18121b9c",
|
||||
|
||||
<comment>// Canonical subject that message was sent to</comment>
|
||||
"subject": {
|
||||
"name": "foobar"
|
||||
},
|
||||
|
||||
<comment>// Server-recorded UNIX timestamp that the server received the message</comment>
|
||||
"created": 1402212355.0,
|
||||
|
||||
<comment>// Deserialized message data</comment>
|
||||
"message": "test",
|
||||
|
||||
<comment>// <a href="#profile">Profile</a> key of sender</comment>
|
||||
"sender": "XXXprofileXXX"
|
||||
}</pre></p>
|
||||
|
||||
<p>Messages are sent by the application using the <a href="#sendMessage">sendMessage()</a>
|
||||
method.</p>
|
||||
|
||||
|
||||
<h4><a name="pin">Pin</a></h4>
|
||||
|
||||
<p>A pin is a <a href="#message">message</a> that is deleted when the
|
||||
<a href="#instance">instance</a> that sent it disconnects. Subscribers to the pin's
|
||||
<a href="#subject">subject</a> are notified (via the <a href="#pin_event">pin</a> and
|
||||
<a href="#unpin_event">unpin</a> events) when it is added and deleted. This allows pins to be
|
||||
used for presence detection.</p>
|
||||
|
||||
<p>Pins are added and deleted by the application using the <a href="#pin_method">pin()</a>
|
||||
and <a href="#unpin">unpin()</a> methods.</p>
|
||||
|
||||
|
||||
<h3><a name="events">Events</a></h3>
|
||||
|
||||
<p>The Cosmopolite client library uses JavaScript Events by implementing
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget">EventTarget</a>.
|
||||
Listening for these events is the primary means to receive notification of changes in status and
|
||||
incoming messages. Event listeners may be added at any time, but are most useful when
|
||||
added immediately after construction. Adding listeners later won't replay events sent in the
|
||||
interim.</p>
|
||||
|
||||
<p>Note that all the event-specific values mentioned below are contained within the Event's
|
||||
<i>detail</i> member, not on the top-level Event object.</p>
|
||||
|
||||
|
||||
<h4><a name="connect">connect</a></h4>
|
||||
|
||||
<p>Values: <i>none</i></p>
|
||||
|
||||
<p>The connect event is called when the <a href="#channel">channel</a> is established to the
|
||||
server. It is paired with the <a href="#disconnect">disconnect</a> event except when
|
||||
<a href="#shutdown">shutdown()</a> is called.</p>
|
||||
|
||||
|
||||
<h4><a name="disconnect">disconnect</a></h4>
|
||||
|
||||
<p>Values: <i>none</i></p>
|
||||
|
||||
<p>The disconnect event fires when the <a href="#channel">channel</a> is disconnected.
|
||||
This can happen due to connectivity problems, server outages or periodically when the channel
|
||||
expires.</p>
|
||||
|
||||
|
||||
<h4><a name="login">login</a></h4>
|
||||
|
||||
<p>Values:
|
||||
<ol>
|
||||
<li><code>username</code>: The user's Google username string.</li>
|
||||
<li><code>logout_url</code>: A URL that the user can load to terminate their session. This should
|
||||
be opened in a new window (using <code>target="_blank"</code>).</li>
|
||||
</ol></p>
|
||||
|
||||
<p>The login event fires when the user logs in. It also fires after the first RPC
|
||||
to the server succeeds if the user was already logged in. It is guaranteed that either login
|
||||
or <a href="#logout">logout</a> will fire after the first RPC succeeds.</p>
|
||||
|
||||
|
||||
<h4><a name="logout">logout</a></h4>
|
||||
|
||||
<p>Values:
|
||||
<ol>
|
||||
<li><code>login_url</code>: A URL that the user cna load to log in to their Google account. This
|
||||
should be opened in a new window (using <code>target="_blank"</code>).</li>
|
||||
</ol></p>
|
||||
|
||||
<p>The login event fires when the user logs out or is logged out by Google. It also
|
||||
fires after the first RPC to the server succeeds if the user was not already logged in. It is
|
||||
guaranteed that either <a href="#login">login</a> or logout fires after the first RPC succeeds.</p>
|
||||
|
||||
|
||||
<h4><a name="message_event">message</a></h4>
|
||||
|
||||
<p>Values:
|
||||
<ol>
|
||||
<li><code>message</code>: A <a href="#message">message</a> object including metadata and message
|
||||
content.</li>
|
||||
</ol></p>
|
||||
|
||||
<p>The message event fires when a historical or new message is received from the server.
|
||||
It only fires for <a href="#subject">subjects</a> that the client is currently subscribed to.
|
||||
</p>
|
||||
|
||||
<p>After a call to <a href="#subscribe">subscribe()</a>, message fires once for each historical
|
||||
message received from the server. After all historical messages have been processed, the resolve
|
||||
callback for the Promise returned by subscribe() is called. Any message events after that point
|
||||
are for new messages.</p>
|
||||
|
||||
<p>Calling <a href="#subscribe">subscribe()</a> more than once may lead to additional historical
|
||||
messages being retreived. message will not fire twice for the same message unless
|
||||
<a href="#unsubscribe">unsubscribe()</a> is called in the interim.</p>
|
||||
|
||||
<p>message fires for messages sent by our own <a href="#profile">profile</a> and
|
||||
<a href="#client">client</a>. You can filter messages by comparing their sender field to the return
|
||||
value of <a href="#currentProfile">currentProfile()</a> to avoid processing your own messages.</p>
|
||||
|
||||
<p>message may fire when we are not connected (per the <a href="#connect">connect</a> and
|
||||
<a href="#disconnect">disconnect</a> events), as messages may be received in RPC responses
|
||||
instead of over the <a href="#channel">channel</a></p>
|
||||
|
||||
|
||||
<h4><a name="pin_event">pin</a></h4>
|
||||
|
||||
<p>Values:
|
||||
<ol>
|
||||
<li><code>message</code>: A <a href="#message">message</a> object including metadata and message
|
||||
content.</li>
|
||||
</ol></p>
|
||||
|
||||
<p>The pin event fires when a new pin is received from the server. It only fires for
|
||||
<a href="#subject">subjects</a> that the client is currently subscribed to.</p>
|
||||
|
||||
<p>After a call to <a href="#subscribe">subscribe()</a>, pin fires once for each pin already
|
||||
present in the subject. After all standing pins have been processed, the resolve callback for the
|
||||
Promise returned by subscribe() is called. Any pin events after that point are for new pins.</p>
|
||||
|
||||
<p>pin events are paired with <a href="#unpin_event">unpin</a> events except when
|
||||
<a href="#shutdown">shutdown()</a> is called.</p>
|
||||
|
||||
<p>pin fires for pins from our own <a href="#profile">profile</a> and <a href="#client">client</a>.
|
||||
You can filter pins by comparing their sender field to the return value of
|
||||
<a href="#currentProfile">currentProfile()</a> to avoid processing your own pins.</p>
|
||||
|
||||
<p>pin may fire when we are not connected (per the <a href="#connect">connect</a> and
|
||||
<a href="#disconnect">disconnect</a> events), as messages may be received in RPC responses
|
||||
instead of over the <a href="#channel">channel</a></p>
|
||||
|
||||
|
||||
<h4><a name="unpin_event">unpin</a></h4>
|
||||
|
||||
<p>Values:
|
||||
<ol>
|
||||
<li><code>message</code>: A <a href="#message">message</a> object including metadata and message
|
||||
content.</li>
|
||||
</ol></p>
|
||||
|
||||
<p>The unpin event fires when a pin is deleted on from the server. It also fires when
|
||||
the <a href="#channel">channel</a> disconnects from the server, hence the server state of pins is
|
||||
unknown.</p>
|
||||
|
||||
<p>unpin events are paired with <a href="#pin_event">pin</a> events except when
|
||||
<a href="#shutdown">shutdown()</a> is called.</p>
|
||||
|
||||
<p>unpin fires for pins from own <a href="#profile">profile</a> and <a href="#client">client</a>.
|
||||
You can filter pins by comparing their sender field to the return value of
|
||||
<a href="#currentProfile">currentProfile()</a> to avoid processing your own pins.</p>
|
||||
|
||||
<p>unpin may fire when we are not connected (per the <a href="#connect">connect</a> and
|
||||
<a href="#disconnect">disconnect</a> events), as messages may be received in RPC responses
|
||||
instead of over the <a href="#channel">channel</a></p>
|
||||
|
||||
|
||||
<h3><a name="methods">Methods</a></h3>
|
||||
|
||||
|
||||
<h4><a name="constructor">Constructor</a></h4>
|
||||
|
||||
<p>Arguments:
|
||||
<ol>
|
||||
<li><code>urlPrefix</code> (optional): A string containing the URL path at which to find the
|
||||
server-side cosmopolite endpoints. Defaults to "/cosmopolite". See the
|
||||
<a href="overview#architecture">architecture</a> diagram.</li>
|
||||
<li><code>namespace</code> (optional): A string containing a namespace prefix to be used for
|
||||
entries in localStorage. Defaults to "cosmopolite". Changing this allows multiple local instances
|
||||
of Cosmopolite to co-exist on the same domain without interfering.</li>
|
||||
<li><code>trackingID</code> (optional): A string containing a
|
||||
<a href="https://support.google.com/analytics/answer/1032385?hl=en">Google Analytics tracking /
|
||||
web property ID</a> (usually starting with "UA-") identifying your analytics account. Passing
|
||||
this argument enables event tracking and the <a href="#trackEvent">trackEvent()</a> method.</li>
|
||||
</ol></p>
|
||||
|
||||
<p>Construct a new Cosmopolite instance. Always use the <code>new</code> keyword. All methods on
|
||||
the instance are available for immediate use, though some deferred initialization is occuring and
|
||||
some actions may be queued.</p>
|
||||
|
||||
</ol>
|
||||
|
||||
|
||||
<h4><a name="shutdown">shutdown()</a></h4>
|
||||
|
||||
<p>Arguments: <i>none</i></p>
|
||||
|
||||
<p>Start shutdown of this instance. No events will be fired after this call. Some RPCs may be
|
||||
outstanding and some cleanup may be deferred.</p>
|
||||
|
||||
|
||||
<h4><a name="shutdown">connected()</a></h4>
|
||||
|
||||
<p>Arguments: <i>none</i></p>
|
||||
|
||||
<p>Returns: A <code>boolean</code> indicating whether we currently believe that we are
|
||||
connected to the server.</p>
|
||||
|
||||
<p>Note that this information may lag by the detection delay built into the channel API.</p>
|
||||
|
||||
|
||||
<h4><a name="getProfile">getProfile()</a></h4>
|
||||
|
||||
<p>Arguments: <i>none</i></p>
|
||||
|
||||
<p>Returns: A
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">
|
||||
Promise</a> to provide a <a href="#profile">profile</a> ID string.</p>
|
||||
|
||||
<p>As the <a href="#profile">profile</a> ID is generated by the server, it requires a round trip
|
||||
to complete successfully before it is known to the client. getProfile() returns a promise that
|
||||
resolves immediately if the profile ID is already known, or fires when it becomes available.</p>
|
||||
|
||||
|
||||
<h4><a name="currentProfile">currentProfile()</a></h4>
|
||||
|
||||
<p>Arguments: <i>none</i></p>
|
||||
|
||||
<p>Returns: A <a href="#profile">profile</a> ID string, or null if unknown.</p>
|
||||
|
||||
<p>currentProfile() has simpler semantics than <a href="getProfile()">getProfile()</a> but may
|
||||
return null if the initial RPC to the server is still outstanding. It is not safe to use just after
|
||||
construction. It is safe to use from event listeners and promises returned by other functions, as
|
||||
those require a server response.</p>
|
||||
|
||||
|
||||
<h4><a name="subscribe">subscribe()</a></h4>
|
||||
|
||||
<p>Arguments:
|
||||
<ol>
|
||||
<li><code>subject</code>: A valid <a href="#subject">subject</a> (string, number or object with
|
||||
specific keys), or an Array of subjects</li>
|
||||
<li><code>messages</code> (optional): An integer number of historical messages to retrieve from
|
||||
this subject if available. 0 means no messages; -1 means all messages.</li>
|
||||
<li><code>lastID</code> (optional): The id field from the last message received by the client.
|
||||
The server will send historical messages starting from the message after that.</li>
|
||||
</ol>
|
||||
|
||||
<p>Returns: A
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">
|
||||
Promise</a> that resolves with no arguments on success, or rejects with an
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error">
|
||||
Error</a> argument if the client is denied access to read the subject. If the <code>subject</code>
|
||||
argument was an Array of subjects, returns an Array of Promises that correspond.</p>
|
||||
|
||||
<p>subscribe() creates a <a href="#subscription">subscription</a> on the server to a particular
|
||||
subject. The subscription is tied to our current <a href="#instance">instance</a> but is re-created
|
||||
by the client library when it reconnects, transparent to the application.</p>
|
||||
|
||||
<p>Before the promise returned by subscribe() resolves, the <a href="#message_event">message</a> and
|
||||
<a href="#pin_event">pin</a> events will fire for any historical messages requests and available
|
||||
and for any current pins. After the promise resolves, <a href="#message">messages</a> and
|
||||
<a href="#pin">pins</a> sent to this subject will cause events to fire when they are
|
||||
received. Additionally, the <a href="#unpin">unpin</a> event will fire for deleted pins.</p>
|
||||
|
||||
<p>Callbacks fire for messages sent by our own <a href="#profile">profile</a> and
|
||||
<a href="#client">client</a>. You can filter messages by comparing their sender field to the return
|
||||
value of <a href="#currentProfile">currentProfile()</a> to avoid processing your own messages.</p>
|
||||
|
||||
<p>subscribe() may be called multiple times for the same subject to retrieve additional historical
|
||||
messages. The subscriptions do not stack, however; a single call to
|
||||
<a href="#unsubscribe">unsubscribe()</a> undoes all preceding subscribe() calls to that subject.
|
||||
</p>
|
||||
|
||||
<p>subscribe() calls may fail (and reject the promise) when subscription is requested to a
|
||||
<a href="#subject">subject</a> that sets <code>readable_only_by</code> and that does not match our
|
||||
profile.</p>
|
||||
|
||||
|
||||
<h4><a name="unsubscribe">unsubscribe()</a></h4>
|
||||
|
||||
<p>Arguments:
|
||||
<ol>
|
||||
<li><code>subject</code>: A valid <a href="#subject">subject</a> (string, number or object with
|
||||
specific keys)</li>
|
||||
</ol></p>
|
||||
|
||||
<p>Returns: A
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">
|
||||
Promise</a> that resolves on RPC completion.</p>
|
||||
|
||||
<p>Unsubscribe from a <a href="#subject">subject</a> and stop receiving
|
||||
<a href="#events">events</a> related to it. Events stop immediately after calling
|
||||
unsubscribe().</p>
|
||||
|
||||
<p>A single call to unsubscribe() undoes all calls to <a href="#subscribe">subscribe()</a> for the
|
||||
given subject.</a>
|
||||
|
||||
|
||||
<h4><a name="sendMessage">sendMessage()</a></h4>
|
||||
|
||||
<p>Arguments:
|
||||
<ol>
|
||||
<li><code>subject</code>: A valid <a href="#subject">subject</a> (string, number or object with
|
||||
specific keys)</li>
|
||||
<li><code>message</code>: The <a href="#message">message</a> data. Any value that JSON can
|
||||
serialize is valid.</li>
|
||||
</ol></p>
|
||||
|
||||
<p>Returns: A
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">
|
||||
Promise</a> that resolves on success, or rejects with an
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error">
|
||||
Error</a> argument if the client is denied access to write to the subject. On success, a
|
||||
<a href="#message">message</a> argument is passed to the resolve callback; it differs from the
|
||||
original <code>message</code> argument in that it has metadata added by the server.</p>
|
||||
|
||||
<p>Sends a <a href="#message">message</a> to the given <a href="#subject">subject</a>. The message
|
||||
contents are serialized and sent to the server (see
|
||||
<a href="overview#architecture">architecture</a>). The server adds metadata (sequence number,
|
||||
timestamp, sender profile ID), stores the message and sends it on to any current
|
||||
<a href="#subscription">subscribers</a> to the subject, including possibly back to the local
|
||||
client. Future subscribers can fetch the historical message from the server's storage (as opposed
|
||||
to a <a href="#pin">pin</a> which is ephemeral and tied to the current
|
||||
<a href="#instance">instance</a>).</p>
|
||||
|
||||
<p>The message may be rejected by server (causing the promise to reject) if the
|
||||
<a href="#subject">subject</a> sets <code>writable_only_by</code> and it does not match our
|
||||
current profile.</p>
|
||||
|
||||
<p>If we are also <a href="#subscribe">subscribed</a> to the given subject and are listening to
|
||||
<a href="#message_event">message</a> events, listeners will fire for this message when it
|
||||
is received back from the server. You can filter messages by comparing their sender field to the
|
||||
return value of <a href="#currentProfile">currentProfile()</a> to avoid processing your own
|
||||
messages.</p>
|
||||
|
||||
|
||||
<h4><a name="getMessages">getMessages()</a></h4>
|
||||
|
||||
<p>Arguments:
|
||||
<ol>
|
||||
<li><code>subject</code>: A valid <a href="#subject">subject</a> (string, number or object with
|
||||
specific keys)</li>
|
||||
</ol></p>
|
||||
|
||||
<p>Returns: An ordered (oldest to newest) array of <a href="#message">message</a> objects.</p>
|
||||
|
||||
<p>The client library keeps a cache of messages received from
|
||||
<a href="#subscription">subscriptions</a>. getMessage() returns the contents of that cache.</p>
|
||||
|
||||
<p>Note that only subjects for which <a href="#subscribe">subscribe()</a> has been called are valid
|
||||
to pass to getMessage(), and that getMessage() only returns historical messages as specified by the
|
||||
arguments to subscribe(). To fetch more historical messages, call subscribe() again with different
|
||||
arguments.</p>
|
||||
|
||||
|
||||
<h4><a name="getLastMessage">getLastMessage()</a></h4>
|
||||
|
||||
<p>Arguments:
|
||||
<ol>
|
||||
<li><code>subject</code>: A valid <a href="#subject">subject</a> (string, number or object with
|
||||
specific keys)</li>
|
||||
</ol></p>
|
||||
|
||||
<p>Returns: The most recent <a href="#message">message</a> object received, or null if none.</p>
|
||||
|
||||
<p>Similar to <a href="#getMessages">getMessages()</a>, but only returns the most recent message
|
||||
received from the server (in server sequence number ordering). This is useful for applications
|
||||
using Cosmopolite as a key/value store that don't need to interact with the historical messages.
|
||||
</p>
|
||||
|
||||
|
||||
<h4><a name="pin_method">pin()</a></h4>
|
||||
|
||||
<p>Arguments:
|
||||
<ol>
|
||||
<li><code>subject</code>: A valid <a href="#subject">subject</a> (string, number or object with
|
||||
specific keys)</li>
|
||||
<li><code>message</code>: The <a href="#message">message</a> data. Any value that JSON can
|
||||
serialize is valid.</li>
|
||||
</ol></p>
|
||||
|
||||
<p>Returns: A
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">
|
||||
Promise</a> that resolves on success, or rejects with no arguments if the client is denied access
|
||||
to write to the subject. If successful, it is passed a single string argument containing a unique
|
||||
identifier for the pin that can later be passed on <a href="#unpin">unpin()</a>.</p>
|
||||
|
||||
<p>A <a href="#pin">pin</a> is like a <a href="#message">message</a>, with some differences: it is
|
||||
tied to the lifetime of the current <a href="#instance">instance</a> and
|
||||
<a href="#channel">channel</a>, it can be deleted by the publishing <a href="#client">client</a>
|
||||
(by calling <a href="#unpin">unpin()</a>, and it is unordered on the server.</p>
|
||||
|
||||
<p>The pin may be rejected by server (causing the promise to reject) if the
|
||||
<a href="#subject">subject</a> sets <code>writable_only_by</code> and it does not match our
|
||||
current profile.</p>
|
||||
|
||||
<p>If we are also <a href="#subscribe">subscribed</a> to the given subject and are listening to
|
||||
<a href="#pin_event">pin</a> events, listeners will fire for this pin when it is received back
|
||||
from the server. You can filter pins by comparing their sender field to the return value of
|
||||
<a href="#currentProfile">currentProfile()</a> to avoid processing your own pins.</p>
|
||||
|
||||
|
||||
<h4><a name="unpin">unpin()</a></h4>
|
||||
|
||||
<p>Arguments:
|
||||
<ol>
|
||||
<li><code>id</code>: An ID string previously passed to the resolve callback of the promise
|
||||
returned by <a href="#pin_method">pin()</a> or the <code>sender_message_id</code> from the
|
||||
<a href="#pin_event">pin</a> event or the return value of <a href="#getPins">getPins()</a>.</p>
|
||||
</ol></p>
|
||||
|
||||
<p>Returns: A
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">
|
||||
Promise</a> that resolves with no arguments on RPC completion</p>
|
||||
|
||||
<p>Remove a currently active <a href="#pin">pin</a>. The pin must have been added by this
|
||||
<a href="#client">client</a>, but not necessarily by this <a href="#instance">instance</a> (the
|
||||
client library automatically re-adds pins after reconnection).</p>
|
||||
|
||||
|
||||
<h4><a name="getPins">getPins()</a></h4>
|
||||
|
||||
<p>Arguments:
|
||||
<ol>
|
||||
<li><code>subject</code>: A valid <a href="#subject">subject</a> (string, number or object with
|
||||
specific keys)</li>
|
||||
</ol></p>
|
||||
|
||||
<p>Returns: An unordered array of <a href="#message">message</a>-like <a href="#pin"> objects.</a>
|
||||
</p>
|
||||
|
||||
<p>The client library keeps a list of currently active pins
|
||||
<a href="#subscription">subscriptions</a>. getPins() returns the contents of that cache.</p>
|
||||
|
||||
|
||||
<h4><a name="trackEvent">trackEvent()</a></h4>
|
||||
|
||||
<p>Arguments: the same as the
|
||||
<a href="https://developers.google.com/analytics/devguides/collection/analyticsjs/method-reference">ga()</a>
|
||||
function from Google Analytics.</p>
|
||||
|
||||
<p>If trackingID was passed to the Cosmopolite <a href="#constructor">constructor</a>, Cosmopolite
|
||||
sends analytics events on load and subscribe. To avoid duplicate work initializing the analytics
|
||||
library, trackEvent() provides a passthrough to the analytics interface.</p>
|
||||
|
||||
<p>Cosmopolite does not send a
|
||||
<a href="https://developers.google.com/analytics/devguides/collection/analyticsjs/pages">pageview</a>
|
||||
event. To tie together tracking data, it is recommended that you send at least one pageview from
|
||||
your application code.</p>
|
||||
|
||||
|
||||
<h4><a name="uuid">uuid()</a></h4>
|
||||
|
||||
<p>Arguments: <i>none</i></p>
|
||||
|
||||
<p>Returns: A new, random universally unique identifier string</p>
|
||||
|
||||
<p>Generate and return a new
|
||||
<a href="https://en.wikipedia.org/wiki/Universally_unique_identifier">UUID</a>. This generates a
|
||||
fully random verison 4 UUID; it is not MAC-based.</p>
|
||||
|
||||
|
||||
</section>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-37845853-2', 'auto');
|
||||
ga('require', 'displayfeatures');
|
||||
ga('send', 'pageview');
|
||||
|
||||
var trackOutboundLink = function(url) {
|
||||
ga('send', 'event', 'outbound', 'click', url, {
|
||||
'hitCallback': function () {
|
||||
document.location = url;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,796 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2014, Ian Gulliver
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// We use long keys in many places. Provide a method to trim those down for
|
||||
// human readability.
|
||||
String.prototype.hashCode = function() {
|
||||
var hash = 0;
|
||||
for (i = 0; i < this.length; i++) {
|
||||
var char = this.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Object=} callbacks Callback dictionary
|
||||
* @param {string=} urlPrefix Absolute URL prefix for generating API URL
|
||||
* @param {string=} namespace Prefix for localStorage entries.
|
||||
*/
|
||||
var Cosmopolite = function(callbacks, urlPrefix, namespace) {
|
||||
this.callbacks_ = callbacks || {};
|
||||
this.urlPrefix_ = urlPrefix || '/cosmopolite';
|
||||
this.namespace_ = namespace || 'cosmopolite';
|
||||
|
||||
this.channelState_ = this.ChannelState.CLOSED;
|
||||
this.shutdown_ = false;
|
||||
|
||||
this.rpcQueue_ = [];
|
||||
this.subscriptions_ = {};
|
||||
this.profilePromises_ = [];
|
||||
|
||||
this.messageQueueKey_ = this.namespace_ + ':message_queue';
|
||||
if (this.messageQueueKey_ in localStorage) {
|
||||
var messages = JSON.parse(localStorage[this.messageQueueKey_]);
|
||||
if (messages.length) {
|
||||
console.log(
|
||||
this.loggingPrefix_(), '(re-)sending queued messages:', messages);
|
||||
}
|
||||
messages.forEach(function(message) {
|
||||
// We don't use sendMessage because we need to preserve the first
|
||||
// message's client_message_id, which is intentionally not exposed via
|
||||
// the sendMessage API
|
||||
this.sendRPC_(
|
||||
'sendMessage', message,
|
||||
this.onMessageSent_.bind(this, message, null, null));
|
||||
}.bind(this));
|
||||
} else {
|
||||
localStorage[this.messageQueueKey_] = JSON.stringify([]);
|
||||
}
|
||||
|
||||
var scriptUrls = [
|
||||
'/_ah/channel/jsapi',
|
||||
];
|
||||
this.numScriptsToLoad_ = scriptUrls.length;
|
||||
scriptUrls.forEach(function(scriptUrl) {
|
||||
var script = document.createElement('script');
|
||||
script.src = scriptUrl;
|
||||
script.onload = this.onLoad_.bind(this);
|
||||
document.body.appendChild(script);
|
||||
}, this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Channel states
|
||||
* @enum {number}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
Cosmopolite.prototype.ChannelState = {
|
||||
// No channel open, no RPC pending
|
||||
CLOSED: 1,
|
||||
// No channel open, RPC pending
|
||||
PENDING: 2,
|
||||
// RPC complete, channel opening
|
||||
OPENING: 3,
|
||||
// Channel opened
|
||||
OPEN: 3,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Subscription states
|
||||
* @enum {number}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
Cosmopolite.prototype.SubscriptionState = {
|
||||
PENDING: 1,
|
||||
ACTIVE: 2,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Shutdown this instance.
|
||||
*
|
||||
* No callbacks will fire after this returns.
|
||||
*/
|
||||
Cosmopolite.prototype.shutdown = function() {
|
||||
console.log(this.loggingPrefix_(), 'shutdown');
|
||||
this.shutdown_ = true;
|
||||
if (this.socket_) {
|
||||
this.socket_.close();
|
||||
}
|
||||
if (this.messageHandler_) {
|
||||
window.removeEventListener('message', this.messageHandler_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Subscribe to a subject.
|
||||
*
|
||||
* Start receiving messages sent to this subject via the onMessage callback.
|
||||
*
|
||||
* @param {!*} subject Subject name or object
|
||||
* @param {number=} messages Number of recent messages to request; 0 for none, -1 for all
|
||||
* @param {number=} last_id ID of last message received; fetch all messages since
|
||||
* @param {Array.<string>=} keys Key names to ensure we receive at least 1 message defining
|
||||
*/
|
||||
Cosmopolite.prototype.subscribe = function(subject, messages, last_id, keys) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var canonicalSubject = this.canonicalSubject_(subject);
|
||||
var subjectString = JSON.stringify(canonicalSubject);
|
||||
if (!(subjectString in this.subscriptions_)) {
|
||||
this.subscriptions_[subjectString] = {
|
||||
'messages': [],
|
||||
'keys': {},
|
||||
'state': this.SubscriptionState.PENDING,
|
||||
};
|
||||
}
|
||||
|
||||
var args = {
|
||||
'subject': canonicalSubject,
|
||||
};
|
||||
if (messages) {
|
||||
args['messages'] = messages;
|
||||
}
|
||||
if (last_id != null) {
|
||||
args['last_id'] = last_id;
|
||||
}
|
||||
if (keys != null) {
|
||||
args['keys'] = keys;
|
||||
}
|
||||
|
||||
this.sendRPC_('subscribe', args, function(response) {
|
||||
// unsubscribe may have been called since we sent the RPC. That's racy
|
||||
// without waiting for the promise, but do our best
|
||||
if (subjectString in this.subscriptions_) {
|
||||
this.subscriptions_[subjectString].state = this.SubscriptionState.ACTIVE;
|
||||
}
|
||||
var result = response['result'];
|
||||
if (result == 'ok') {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Unsubscribe from a subject and destroy all listeners.
|
||||
*
|
||||
* Note that no reference counting is done, so a single call to unsubscribe()
|
||||
* undoes multiple calls to subscribe().
|
||||
*
|
||||
* @param {!string} subject Subject name, as passed to subscribe()
|
||||
*/
|
||||
Cosmopolite.prototype.unsubscribe = function(subject) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var canonicalSubject = this.canonicalSubject_(subject);
|
||||
var subjectString = JSON.stringify(canonicalSubject);
|
||||
delete this.subscriptions_[subjectString];
|
||||
var args = {
|
||||
'subject': canonicalSubject,
|
||||
}
|
||||
this.sendRPC_('unsubscribe', args, resolve);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Post a message to the given subject, storing it and notifying all listeners.
|
||||
*
|
||||
* @param {!string} subject Subject name
|
||||
* @param {!*} message Message string or object
|
||||
* @param {string=} key Key name to associate this message with
|
||||
*/
|
||||
Cosmopolite.prototype.sendMessage = function(subject, message, key) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var args = {
|
||||
'subject': this.canonicalSubject_(subject),
|
||||
'message': JSON.stringify(message),
|
||||
'sender_message_id': this.uuid_(),
|
||||
};
|
||||
if (key) {
|
||||
args['key'] = key;
|
||||
}
|
||||
|
||||
// No message left behind.
|
||||
var messageQueue = JSON.parse(localStorage[this.messageQueueKey_]);
|
||||
messageQueue.push(args);
|
||||
localStorage[this.messageQueueKey_] = JSON.stringify(messageQueue);
|
||||
|
||||
this.sendRPC_(
|
||||
'sendMessage', args,
|
||||
this.onMessageSent_.bind(this, args, resolve, reject));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch all received messages for a subject
|
||||
*
|
||||
* @param {!string} subject Subject name
|
||||
* @const
|
||||
*/
|
||||
Cosmopolite.prototype.getMessages = function(subject) {
|
||||
var canonicalSubject = this.canonicalSubject_(subject);
|
||||
var subjectString = JSON.stringify(canonicalSubject);
|
||||
return this.subscriptions_[subjectString].messages;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch the most recent message that defined a key
|
||||
*
|
||||
* @param {!string} subject Subject name
|
||||
* @param {!string} key Key name
|
||||
* @const
|
||||
*/
|
||||
Cosmopolite.prototype.getKeyMessage = function(subject, key) {
|
||||
var canonicalSubject = this.canonicalSubject_(subject);
|
||||
var subjectString = JSON.stringify(canonicalSubject);
|
||||
return this.subscriptions_[subjectString].keys[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a Promise for our profile ID.
|
||||
*/
|
||||
Cosmopolite.prototype.getProfile = function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (this.profile_) {
|
||||
resolve(this.profile_);
|
||||
} else {
|
||||
this.profilePromises_.push(resolve);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Return our current profile ID, if known.
|
||||
*
|
||||
* @return {?string} Profile ID.
|
||||
* @const
|
||||
*/
|
||||
Cosmopolite.prototype.currentProfile = function() {
|
||||
return this.profile_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a string identifying us to be included in log messages.
|
||||
*
|
||||
* @return {string} Log line prefix.
|
||||
* @const
|
||||
*/
|
||||
Cosmopolite.prototype.loggingPrefix_ = function() {
|
||||
return 'cosmopolite (' + this.namespace_ + '):';
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a v4 UUID.
|
||||
*
|
||||
* @return {string} A universally-unique random value.
|
||||
* @const
|
||||
*/
|
||||
Cosmopolite.prototype.uuid_ = function() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = (Math.random() * 16) | 0;
|
||||
if (c == 'x') {
|
||||
return r.toString(16);
|
||||
} else {
|
||||
return (r & (0x03 | 0x08)).toString(16);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Canonicalize a subject name or object
|
||||
*
|
||||
* @param {!*} subject A simple or complex representation of a subject
|
||||
* @return {Object} A canonicalized object for RPCs
|
||||
*/
|
||||
Cosmopolite.prototype.canonicalSubject_ = function(subject) {
|
||||
if (typeof(subject) == 'number') {
|
||||
subject = subject.toString();
|
||||
}
|
||||
if (typeof(subject) == 'string') {
|
||||
subject = {
|
||||
'name': subject,
|
||||
}
|
||||
}
|
||||
if (subject['readable_only_by'] === null) {
|
||||
delete subject['readable_only_by'];
|
||||
};
|
||||
if (subject['writable_only_by'] === null) {
|
||||
delete subject['writable_only_by'];
|
||||
};
|
||||
return subject;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback when a script loads.
|
||||
*/
|
||||
Cosmopolite.prototype.onLoad_ = function() {
|
||||
if (--this.numScriptsToLoad_ > 0) {
|
||||
return;
|
||||
}
|
||||
if (this.shutdown_) {
|
||||
// Shutdown during startup
|
||||
return;
|
||||
}
|
||||
this.registerMessageHandlers_();
|
||||
this.createChannel_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for a message from another browser window
|
||||
*
|
||||
* @param {!string} data Message contents
|
||||
*/
|
||||
Cosmopolite.prototype.onReceiveMessage_ = function(data) {
|
||||
switch (data) {
|
||||
case 'login_complete':
|
||||
if (this.socket_) {
|
||||
this.socket_.close();
|
||||
}
|
||||
break;
|
||||
case 'logout_complete':
|
||||
localStorage.removeItem(this.namespace_ + ':client_id');
|
||||
localStorage.removeItem(this.namespace_ + ':google_user_id');
|
||||
if (this.socket_) {
|
||||
this.socket_.close();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log(this.loggingPrefix_(), 'unknown event type:', data);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Register onReceiveMessage to receive callbacks
|
||||
*
|
||||
* Note that we share this bus with at least the channel code, so spurious
|
||||
* messages are normal.
|
||||
*/
|
||||
Cosmopolite.prototype.registerMessageHandlers_ = function() {
|
||||
this.messageHandler_ = function(e) {
|
||||
if (e.origin != window.location.origin) {
|
||||
// Probably talkgadget
|
||||
return;
|
||||
}
|
||||
console.log(this.loggingPrefix_(), 'received browser message:', e.data);
|
||||
this.onReceiveMessage_(e.data);
|
||||
}.bind(this);
|
||||
window.addEventListener('message', this.messageHandler_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for a sendMessage RPC ack by the server.
|
||||
*
|
||||
* @param {Object} message Message details.
|
||||
* @param {function()=} resolve Promise resolution callback.
|
||||
* @param {function()=} reject Promise rejection callback.
|
||||
* @param {Object=} response Server RPC response.
|
||||
*/
|
||||
Cosmopolite.prototype.onMessageSent_ = function(
|
||||
message, resolve, reject, response) {
|
||||
// No message left behind.
|
||||
var messageQueue = JSON.parse(localStorage[this.messageQueueKey_]);
|
||||
messageQueue = messageQueue.filter(function(queuedMessage) {
|
||||
return message['sender_message_id'] != queuedMessage['sender_message_id'];
|
||||
});
|
||||
localStorage[this.messageQueueKey_] = JSON.stringify(messageQueue);
|
||||
var result = response['result'];
|
||||
if (result == 'ok' || result == 'duplicate_message') {
|
||||
if (resolve) {
|
||||
resolve();
|
||||
}
|
||||
} else {
|
||||
if (reject) {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a single RPC to the server.
|
||||
*
|
||||
* See sendRPCs_()
|
||||
*
|
||||
* @param {!string} command Command name to call
|
||||
* @param {!Object} args Arguments to pass to server
|
||||
* @param {function(Object)=} onSuccess Success callback function
|
||||
*/
|
||||
Cosmopolite.prototype.sendRPC_ = function(command, args, onSuccess) {
|
||||
var rpc = {
|
||||
'command': command,
|
||||
'arguments': args,
|
||||
'onSuccess': onSuccess,
|
||||
};
|
||||
if (this.maySendRPC_()) {
|
||||
this.sendRPCs_([rpc]);
|
||||
} else {
|
||||
// Queue instead of sending.
|
||||
this.rpcQueue_.push(rpc);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send one or more RPCs to the server.
|
||||
*
|
||||
* Wraps handling of authentication to the server, even in cases where we need
|
||||
* to retry with more data. Also retries in cases of failure with exponential
|
||||
* backoff.
|
||||
*
|
||||
* @param {!Array.<{command:string, arguments:Object, onSuccess:function(Object)}>} commands List of commands to execute
|
||||
* @param {number=} delay Seconds waited before executing this call (for backoff)
|
||||
*/
|
||||
Cosmopolite.prototype.sendRPCs_ = function(commands, delay) {
|
||||
if (this.shutdown_ || !commands.length) {
|
||||
return;
|
||||
}
|
||||
var request = {
|
||||
'commands': [],
|
||||
};
|
||||
commands.forEach(function(command) {
|
||||
var request_command = {
|
||||
'command': command['command'],
|
||||
};
|
||||
if ('arguments' in command) {
|
||||
request_command['arguments'] = command['arguments'];
|
||||
}
|
||||
request.commands.push(request_command);
|
||||
});
|
||||
if (this.namespace_ + ':client_id' in localStorage) {
|
||||
request['client_id'] = localStorage[this.namespace_ + ':client_id'];
|
||||
}
|
||||
if (this.namespace_ + ':google_user_id' in localStorage) {
|
||||
request['google_user_id'] = localStorage[this.namespace_ + ':google_user_id'];
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.responseType = 'json';
|
||||
|
||||
var retryAfterDelay = function() {
|
||||
var intDelay =
|
||||
xhr.getResponseHeader('Retry-After') ||
|
||||
Math.min(32, Math.max(2, delay || 2));
|
||||
console.log(
|
||||
this.loggingPrefix_(),
|
||||
'RPC failed; will retry in ' + intDelay + ' seconds');
|
||||
var retry = function() {
|
||||
this.sendRPCs_(commands, Math.pow(intDelay, 2));
|
||||
}.bind(this);
|
||||
window.setTimeout(retry, intDelay * 1000);
|
||||
}.bind(this);
|
||||
|
||||
xhr.addEventListener('load', function(e) {
|
||||
if (xhr.status != 200) {
|
||||
retryAfterDelay();
|
||||
return;
|
||||
}
|
||||
var data = xhr.response;
|
||||
|
||||
if ('google_user_id' in data) {
|
||||
localStorage[this.namespace_ + ':google_user_id'] =
|
||||
data['google_user_id'];
|
||||
}
|
||||
if ('client_id' in data) {
|
||||
localStorage[this.namespace_ + ':client_id'] = data['client_id'];
|
||||
}
|
||||
|
||||
if (data['status'] == 'retry') {
|
||||
// Discard delay
|
||||
this.sendRPCs_(commands);
|
||||
return;
|
||||
}
|
||||
if (data['status'] != 'ok') {
|
||||
console.log(this.loggingPrefix_(),
|
||||
'server returned unknown status:', data['status']);
|
||||
// TODO(flamingcow): Refresh the page? Show an alert?
|
||||
return;
|
||||
}
|
||||
|
||||
this.flushRPCQueue_();
|
||||
|
||||
// Handle events that were immediately available as if they came over the
|
||||
// channel. Fire them before the message callbacks, so clients can use
|
||||
// events like the subscribe promise fulfillment as a barrier for initial
|
||||
// data.
|
||||
data['events'].forEach(this.onServerEvent_, this);
|
||||
|
||||
for (var i = 0; i < data['responses'].length; i++) {
|
||||
if (commands[i]['onSuccess']) {
|
||||
commands[i]['onSuccess'].bind(this)(data['responses'][i]);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
xhr.addEventListener('error', retryAfterDelay);
|
||||
xhr.open('POST', this.urlPrefix_ + '/api');
|
||||
xhr.send(JSON.stringify(request));
|
||||
};
|
||||
|
||||
/**
|
||||
* Are we currently clear to put RPCs on the wire?
|
||||
*
|
||||
* @return {Boolean} Yes or no?
|
||||
*/
|
||||
Cosmopolite.prototype.maySendRPC_ = function() {
|
||||
if (!(this.namespace_ + ':client_id' in localStorage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.channelState_ != this.ChannelState.OPEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send queued RPCs
|
||||
*/
|
||||
Cosmopolite.prototype.flushRPCQueue_ = function() {
|
||||
if (!this.maySendRPC_() || !this.rpcQueue_.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendRPCs_(this.rpcQueue_);
|
||||
this.rpcQueue_ = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Resubscribe to subjects (i.e. after reconnection)
|
||||
*/
|
||||
Cosmopolite.prototype.resubscribe_ = function() {
|
||||
var rpcs = [];
|
||||
for (var subject in this.subscriptions_) {
|
||||
var subscription = this.subscriptions_[subject];
|
||||
var canonicalSubject = JSON.parse(subject);
|
||||
if (subscription.state != this.SubscriptionState.ACTIVE) {
|
||||
continue;
|
||||
}
|
||||
var last_id = 0;
|
||||
if (subscription.messages.length > 0) {
|
||||
last_id = subscription.messages[subscription.messages.length - 1]['id'];
|
||||
}
|
||||
rpcs.push({
|
||||
'command': 'subscribe',
|
||||
'arguments': {
|
||||
'subject': canonicalSubject,
|
||||
'last_id': last_id,
|
||||
}
|
||||
});
|
||||
}
|
||||
this.sendRPCs_(rpcs);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send RPC to create a server -> client channel
|
||||
*/
|
||||
Cosmopolite.prototype.createChannel_ = function() {
|
||||
if (this.channelState_ == this.ChannelState.CLOSED) {
|
||||
this.channelState_ = this.ChannelState.PENDING;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
var rpcs = [
|
||||
{
|
||||
'command': 'createChannel',
|
||||
'onSuccess': this.onCreateChannel_,
|
||||
},
|
||||
];
|
||||
// sendRPCs instead of sendRPC so we don't queue.
|
||||
this.sendRPCs_(rpcs);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for channel creation on the server side
|
||||
*
|
||||
* @suppress {missingProperties}
|
||||
*
|
||||
* @param {!Object} data Server response including channel token
|
||||
*/
|
||||
Cosmopolite.prototype.onCreateChannel_ = function(data) {
|
||||
if (this.shutdown_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.channelState_ == this.ChannelState.PENDING) {
|
||||
this.channelState_ = this.ChannelState.OPENING;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
var channel = new goog.appengine.Channel(data['token']);
|
||||
console.log(this.loggingPrefix_(), 'opening channel:', data['token']);
|
||||
this.socket_ = channel.open({
|
||||
onopen: this.onSocketOpen_.bind(this),
|
||||
onclose: this.onSocketClose_.bind(this),
|
||||
onmessage: this.onSocketMessage_.bind(this),
|
||||
onerror: this.onSocketError_.bind(this),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback from channel library for successful open
|
||||
*/
|
||||
Cosmopolite.prototype.onSocketOpen_ = function() {
|
||||
console.log(this.loggingPrefix_(), 'channel opened');
|
||||
|
||||
if (this.shutdown_ && this.socket_) {
|
||||
this.socket_.close();
|
||||
};
|
||||
|
||||
if (this.channelState_ == this.ChannelState.OPENING) {
|
||||
this.channelState_ = this.ChannelState.OPEN;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
this.flushRPCQueue_();
|
||||
this.resubscribe_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback from channel library for closure; reopen.
|
||||
*/
|
||||
Cosmopolite.prototype.onSocketClose_ = function() {
|
||||
console.log(this.loggingPrefix_(), 'channel closed');
|
||||
|
||||
if (this.shutdown_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.channelState_ == this.ChannelState.OPEN) {
|
||||
this.channelState_ = this.ChannelState.CLOSED;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
this.createChannel_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback from channel library for message reception over channel
|
||||
*
|
||||
* @param {!Object} msg Message contents
|
||||
*/
|
||||
Cosmopolite.prototype.onSocketMessage_ = function(msg) {
|
||||
this.onServerEvent_(JSON.parse(msg.data));
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback from channel library for error on channel
|
||||
*
|
||||
* @param {!string} msg Descriptive text
|
||||
*/
|
||||
Cosmopolite.prototype.onSocketError_ = function(msg) {
|
||||
console.log(this.loggingPrefix_(), 'socket error:', msg);
|
||||
if (this.socket_) {
|
||||
this.socket_.close();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback on receiving a 'login' event from the server
|
||||
*
|
||||
* @param {!Object} e Event object
|
||||
*/
|
||||
Cosmopolite.prototype.onLogin_ = function(e) {
|
||||
if ('onLogin' in this.callbacks_) {
|
||||
this.callbacks_['onLogin'](
|
||||
e['google_user'],
|
||||
this.urlPrefix_ + '/auth/logout');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback on receiving a 'logout' event from the server
|
||||
*
|
||||
* @param {!Object} e Event object
|
||||
*/
|
||||
Cosmopolite.prototype.onLogout_ = function(e) {
|
||||
if ('onLogout' in this.callbacks_) {
|
||||
this.callbacks_['onLogout'](
|
||||
this.urlPrefix_ + '/auth/login');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback on receiving a 'message' event from the server
|
||||
*
|
||||
* @param {!Object} e Event object
|
||||
*/
|
||||
Cosmopolite.prototype.onMessage_ = function(e) {
|
||||
var subjectString = JSON.stringify(e['subject']);
|
||||
var subscription = this.subscriptions_[subjectString];
|
||||
if (!subscription) {
|
||||
console.log(
|
||||
this.loggingPrefix_(),
|
||||
'message from unrecognized subject:', e);
|
||||
return;
|
||||
}
|
||||
var duplicate = subscription.messages.some(function(message) {
|
||||
return message['id'] == e.id;
|
||||
});
|
||||
if (duplicate) {
|
||||
console.log(this.loggingPrefix_(), 'duplicate message:', e);
|
||||
return;
|
||||
}
|
||||
e['message'] = JSON.parse(e['message']);
|
||||
|
||||
// Reverse search for the position to insert this message, as iit will most
|
||||
// likely be at the end.
|
||||
var insertAfter;
|
||||
for (var insertAfter = subscription.messages.length - 1;
|
||||
insertAfter >= 0; insertAfter--) {
|
||||
var message = subscription.messages[insertAfter];
|
||||
if (message['id'] < e['id']) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
subscription.messages.splice(insertAfter + 1, 0, e);
|
||||
|
||||
if (e['key']) {
|
||||
subscription.keys[e['key']] = e;
|
||||
}
|
||||
if ('onMessage' in this.callbacks_) {
|
||||
this.callbacks_['onMessage'](e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for Cosmopolite event (received via channel or pseudo-channel)
|
||||
*
|
||||
* @param {!Object} e Deserialized event object
|
||||
*/
|
||||
Cosmopolite.prototype.onServerEvent_ = function(e) {
|
||||
if (this.shutdown_) {
|
||||
return;
|
||||
}
|
||||
if (e['profile']) {
|
||||
this.profile_ = e['profile'];
|
||||
this.profilePromises_.forEach(function(resolve) {
|
||||
resolve(this.profile_);
|
||||
}.bind(this));
|
||||
this.profilePromises_ = [];
|
||||
}
|
||||
switch (e['event_type']) {
|
||||
case 'login':
|
||||
this.onLogin_(e);
|
||||
break;
|
||||
case 'logout':
|
||||
this.onLogout_(e);
|
||||
break;
|
||||
case 'message':
|
||||
this.onMessage_(e);
|
||||
break;
|
||||
default:
|
||||
// Client out of date? Force refresh?
|
||||
console.log(this.loggingPrefix_(), 'unknown channel event:', e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/* Exported values */
|
||||
window.Cosmopolite = Cosmopolite;
|
||||
@@ -1,79 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Cosmopolite Unity Demo</title>
|
||||
<script type='text/javascript' src='https://ssl-webplayer.unity3d.com/download_webplayer-3.x/3.0/uo/jquery.min.js'></script>
|
||||
<script type='text/javascript' src='https://ssl-webplayer.unity3d.com/download_webplayer-3.x/3.0/uo/UnityObject.js'></script>
|
||||
<script type="text/javascript" src="/cosmopolite/static/cosmopolite.js" charset="UTF-8"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="unityPlayer"></div>
|
||||
<script type="text/javascript">
|
||||
var gameObject;
|
||||
var gameModuleName;
|
||||
var messageQueue = [];
|
||||
|
||||
var onUnityLoad = function(obj) {
|
||||
gameObject = obj.ref;
|
||||
};
|
||||
|
||||
var sendUnityMessage = function(funcName, msg) {
|
||||
msg = msg || '';
|
||||
if (gameObject && gameModuleName) {
|
||||
gameObject.SendMessage(gameModuleName, funcName, msg);
|
||||
} else {
|
||||
messageQueue.push([funcName, msg]);
|
||||
}
|
||||
};
|
||||
|
||||
var loginUrl, logoutUrl;
|
||||
|
||||
var doLogin = function() {
|
||||
if (loginUrl) {
|
||||
window.open(loginUrl);
|
||||
}
|
||||
};
|
||||
|
||||
var doLogout = function() {
|
||||
if (logoutUrl) {
|
||||
window.open(logoutUrl);
|
||||
}
|
||||
};
|
||||
|
||||
var registerGameObject = function(name) {
|
||||
gameModuleName = name;
|
||||
while (messageQueue.length) {
|
||||
var item = messageQueue.shift();
|
||||
sendUnityMessage(item[0], item[1]);
|
||||
}
|
||||
};
|
||||
|
||||
var pluginDidLoad = function() {
|
||||
// Called by the Unity example code, but not used.
|
||||
};
|
||||
|
||||
unityObject.embedUnity(
|
||||
'unityPlayer',
|
||||
'debug.unity3d',
|
||||
500,
|
||||
80,
|
||||
null,
|
||||
null,
|
||||
onUnityLoad);
|
||||
|
||||
var callbacks = {
|
||||
onLogin: function(username, logout_url) {
|
||||
logoutUrl = logout_url;
|
||||
sendUnityMessage('OnLogin', username);
|
||||
},
|
||||
onLogout: function(login_url) {
|
||||
loginUrl = login_url;
|
||||
sendUnityMessage('OnLogout');
|
||||
},
|
||||
onStateChange: function(key, value) {
|
||||
},
|
||||
};
|
||||
|
||||
var cosmo = new Cosmopolite(callbacks);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,135 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="/cosmopolite/static/cosmopolite.js" charset="UTF-8"></script>
|
||||
<style>
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>Google user: <span id="google_user"></span></div>
|
||||
|
||||
<div>
|
||||
Subject: <input type="text" id="subject">
|
||||
<input type="button" id="subscribe" value="Subscribe">
|
||||
</div>
|
||||
<div>
|
||||
<select id="subscriptions"></select>
|
||||
<input type="button" id="unsubscribe" value="Unsubscribe">
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div>
|
||||
Message: <input type="text" id="message">
|
||||
</div>
|
||||
<div>
|
||||
Key: <input type="text" id="key">
|
||||
</div>
|
||||
<div>
|
||||
<input type="button" id="send" value="Send">
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div id="keys"></div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div id="messages"></div>
|
||||
|
||||
<script>
|
||||
var subject = document.getElementById('subject');
|
||||
var subscriptions = document.getElementById('subscriptions');
|
||||
|
||||
var message = document.getElementById('message');
|
||||
var key = document.getElementById('key');
|
||||
|
||||
var keys = document.getElementById('keys');
|
||||
|
||||
var messages = document.getElementById('messages');
|
||||
|
||||
var addMessage = function(message) {
|
||||
var messageDiv = document.createElement('div');
|
||||
messageDiv.appendChild(document.createTextNode(
|
||||
(new Date(message['created'] * 1000)).toString() +
|
||||
' <????-' + (Math.abs(message['sender'].hashCode()) % 10000) + '> ' +
|
||||
message['message']
|
||||
));
|
||||
messages.appendChild(messageDiv);
|
||||
};
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
var googleUser = document.getElementById('google_user');
|
||||
|
||||
var callbacks = {
|
||||
onLogin: function(username, logout_url) {
|
||||
googleUser.innerHTML =
|
||||
username +
|
||||
' <a href="' + logout_url + '" target="_blank">(log out)</a>';
|
||||
},
|
||||
onLogout: function(login_url) {
|
||||
googleUser.innerHTML =
|
||||
'<a href="' + login_url + '" target="_blank">(log in)</a>';
|
||||
},
|
||||
onMessage: function(message) {
|
||||
if (subscriptions.value != message['subject']['name']) {
|
||||
return;
|
||||
}
|
||||
addMessage(message);
|
||||
if (message['key']) {
|
||||
var i;
|
||||
for (i = 0; i < keys.childNodes.length; i++) {
|
||||
var key = keys.childNodes[i];
|
||||
if (key.message['key'] == message['key']) {
|
||||
// Overwrite
|
||||
key.replaceChild(
|
||||
document.createTextNode(message['message']),
|
||||
key.childNodes[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == keys.childNodes.length) {
|
||||
var keyDiv = document.createElement('div');
|
||||
keyDiv.appendChild(document.createTextNode(message['key'] + ' = '));
|
||||
keyDiv.appendChild(document.createTextNode(message['message']));
|
||||
keyDiv.message = message;
|
||||
keys.appendChild(keyDiv);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var debug = new Cosmopolite(callbacks);
|
||||
|
||||
document.getElementById('subscribe').addEventListener('click', function() {
|
||||
debug.subscribe(subject.value, -1);
|
||||
var option = document.createElement('option');
|
||||
option.text = subject.value;
|
||||
subscriptions.options.add(option);
|
||||
subscriptions.dispatchEvent(new CustomEvent('change'));
|
||||
});
|
||||
|
||||
document.getElementById('unsubscribe').addEventListener('click', function() {
|
||||
debug.unsubscribe(subscriptions.value);
|
||||
subscriptions.options.remove(subscriptions.options.selectedIndex);
|
||||
subscriptions.dispatchEvent(new CustomEvent('change'));
|
||||
});
|
||||
|
||||
document.getElementById('send').addEventListener('click', function() {
|
||||
debug.sendMessage(subscriptions.value, message.value, key.value);
|
||||
});
|
||||
|
||||
document.getElementById('subscriptions').addEventListener('change', function() {
|
||||
messages.innerHTML = '';
|
||||
if (!subscriptions.value) {
|
||||
return;
|
||||
}
|
||||
debug.getMessages(subscriptions.value).forEach(addMessage);
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
@@ -1,8 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
window.opener.postMessage('login_complete', window.location.origin);
|
||||
window.close();
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
||||
@@ -1,8 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
window.opener.postMessage('logout_complete', window.location.origin);
|
||||
window.close();
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
||||
@@ -1,14 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Cosmopolite tests</title>
|
||||
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-git.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="qunit"></div>
|
||||
<div id="qunit-fixture"></div>
|
||||
<script src="https://code.jquery.com/qunit/qunit-git.js"></script>
|
||||
<script src="/cosmopolite/static/cosmopolite.js" charset="UTF-8"></script>
|
||||
<script src="test.js" charset="UTF-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
508
static/test.js
508
static/test.js
@@ -1,508 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2014, Ian Gulliver
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
A quick note on testing philosophy:
|
||||
|
||||
These tests cover both the client (JavaScript) and the server (Python), as
|
||||
well as the server's interaction with appengine (via dev_appserver or a
|
||||
deployed instance). There is intentionally no mock server. The client and
|
||||
server interactions are complex and tests should be structured to verify them,
|
||||
not to verify the behavior of a simulation.
|
||||
*/
|
||||
|
||||
/*
|
||||
These tests break if you turn on global pollution detection because of at
|
||||
least:
|
||||
|
||||
* goog: goog.appengine.Channel seems to always be global.
|
||||
* closure_lm_*: The Channel code has a bug that puts this in globals.
|
||||
*/
|
||||
|
||||
var randstring = function() {
|
||||
var ret = [];
|
||||
for (var i = 0; i < 16; i++) {
|
||||
var ran = (Math.random() * 16) | 0;
|
||||
ret.push(ran.toString(16));
|
||||
}
|
||||
return ret.join('');
|
||||
};
|
||||
|
||||
var logout = function(callback) {
|
||||
var innerCallback = function(e) {
|
||||
window.removeEventListener('message', innerCallback);
|
||||
if (e.origin != window.location.origin ||
|
||||
e.data != 'logout_complete') {
|
||||
return;
|
||||
}
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', innerCallback);
|
||||
window.open('/cosmopolite/auth/logout');
|
||||
};
|
||||
|
||||
QUnit.testStart(localStorage.clear.bind(localStorage));
|
||||
QUnit.testDone(localStorage.clear.bind(localStorage));
|
||||
|
||||
module('All platforms');
|
||||
|
||||
test('Construct/shutdown', function() {
|
||||
expect(2);
|
||||
var cosmo = new Cosmopolite({}, null, randstring());
|
||||
ok(true, 'new Cosmopolite() succeeds');
|
||||
cosmo.shutdown();
|
||||
ok(true, 'shutdown() succeeds');
|
||||
});
|
||||
|
||||
asyncTest('onLogout fires', function() {
|
||||
expect(1);
|
||||
|
||||
logout(function() {
|
||||
var callbacks = {
|
||||
'onLogout': function(login_url) {
|
||||
ok(true, 'onLogout fired');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
}
|
||||
};
|
||||
var cosmo = new Cosmopolite(callbacks, null, randstring());
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest('Message round trip', function() {
|
||||
expect(2);
|
||||
|
||||
var subject = randstring();
|
||||
var message = randstring();
|
||||
|
||||
var callbacks = {
|
||||
'onMessage': function(e) {
|
||||
equal(e['subject']['name'], subject, 'subject matches');
|
||||
equal(e['message'], message, 'message matches');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
},
|
||||
};
|
||||
|
||||
var cosmo = new Cosmopolite(callbacks, null, randstring());
|
||||
cosmo.sendMessage(subject, message);
|
||||
cosmo.subscribe(subject, -1);
|
||||
});
|
||||
|
||||
asyncTest('Overwrite key', function() {
|
||||
expect(8);
|
||||
|
||||
var subject = randstring();
|
||||
var message1 = randstring();
|
||||
var message2 = randstring();
|
||||
var key = randstring();
|
||||
|
||||
var messages = 0;
|
||||
|
||||
var callbacks = {
|
||||
'onMessage': function(e) {
|
||||
messages++;
|
||||
equal(e['subject']['name'], subject, 'subject matches');
|
||||
equal(e['key'], key, 'key matches');
|
||||
if (messages == 1) {
|
||||
equal(e['message'], message1, 'message #1 matches');
|
||||
equal(cosmo.getKeyMessage(subject, key)['message'], message1, 'message #1 matches by key')
|
||||
cosmo.sendMessage(subject, message2, key);
|
||||
return;
|
||||
}
|
||||
equal(e['message'], message2, 'message #2 matches');
|
||||
equal(cosmo.getKeyMessage(subject, key)['message'], message2, 'message #2 matches by key')
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
},
|
||||
};
|
||||
|
||||
var cosmo = new Cosmopolite(callbacks, null, randstring());
|
||||
cosmo.subscribe(subject, -1);
|
||||
cosmo.sendMessage(subject, message1, key);
|
||||
});
|
||||
|
||||
asyncTest('Complex object', function() {
|
||||
expect(2);
|
||||
|
||||
var subject = randstring();
|
||||
var message = {
|
||||
'foo': 'bar',
|
||||
5: 'zig',
|
||||
'zag': [16, 22, 59, 76],
|
||||
'boo': {
|
||||
'nested': 'object',
|
||||
10: 100,
|
||||
},
|
||||
'unicode': '☠☣☃𠜎',
|
||||
};
|
||||
|
||||
var callbacks = {
|
||||
'onMessage': function(e) {
|
||||
equal(e['subject']['name'], subject, 'subject matches');
|
||||
deepEqual(e['message'], message, 'message matches');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
},
|
||||
};
|
||||
|
||||
var cosmo = new Cosmopolite(callbacks, null, randstring());
|
||||
cosmo.sendMessage(subject, message);
|
||||
cosmo.subscribe(subject, -1);
|
||||
});
|
||||
|
||||
asyncTest('sendMessage Promise', function() {
|
||||
expect(1);
|
||||
|
||||
var subject = randstring();
|
||||
var message = randstring();
|
||||
|
||||
var cosmo = new Cosmopolite({}, null, randstring());
|
||||
cosmo.sendMessage(subject, message).then(function() {
|
||||
ok(true, 'sendMessage Promise fulfilled');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest('subscribe/unsubscribe Promise', function() {
|
||||
expect(2);
|
||||
|
||||
var subject = randstring();
|
||||
var message = randstring();
|
||||
|
||||
var cosmo = new Cosmopolite({}, null, randstring());
|
||||
cosmo.subscribe(subject).then(function() {
|
||||
ok(true, 'subscribe Promise fulfilled');
|
||||
cosmo.unsubscribe(subject).then(function() {
|
||||
ok(true, 'unsubscribe Promise fulfilled');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest('Duplicate message suppression', function() {
|
||||
expect(3);
|
||||
|
||||
var subject = randstring();
|
||||
var key = randstring();
|
||||
var message1 = randstring();
|
||||
var message2 = randstring();
|
||||
|
||||
var callbacks = {
|
||||
'onMessage': function (msg) {
|
||||
equal(msg['subject']['name'], subject, 'subject matches');
|
||||
equal(msg['key'], key, 'key matches');
|
||||
equal(msg['message'], message1, 'message matches');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
},
|
||||
};
|
||||
|
||||
var cosmo = new Cosmopolite(callbacks, null, randstring());
|
||||
// Break cosmo's UUID generator so that it generates duplicate values.
|
||||
cosmo.uuid_ = function() {
|
||||
return '4';
|
||||
// chosen by fair dice roll.
|
||||
// guaranteed to be random.
|
||||
};
|
||||
cosmo.sendMessage(subject, message1, key).then(function() {
|
||||
cosmo.sendMessage(subject, message2, key).then(function() {
|
||||
cosmo.subscribe(subject, 0, null, [key]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest('Message persistence', function() {
|
||||
expect(2);
|
||||
|
||||
var subject = randstring();
|
||||
var message = randstring();
|
||||
var namespace = randstring();
|
||||
|
||||
// Send a message and shut down too fast for it to hit the wire.
|
||||
var cosmo1 = new Cosmopolite({}, null, namespace);
|
||||
cosmo1.sendMessage(subject, message);
|
||||
cosmo1.shutdown();
|
||||
|
||||
var callbacks = {
|
||||
'onMessage': function(msg) {
|
||||
equal(msg['subject']['name'], subject, 'subject matches');
|
||||
equal(msg['message'], message, 'message matches');
|
||||
cosmo2.shutdown();
|
||||
start();
|
||||
},
|
||||
};
|
||||
|
||||
var cosmo2 = new Cosmopolite(callbacks, null, namespace);
|
||||
cosmo2.subscribe(subject, -1);
|
||||
// Should pick up the message from the persistent queue.
|
||||
});
|
||||
|
||||
test('getMessages/subscribe', function() {
|
||||
expect(2);
|
||||
|
||||
var subject = randstring();
|
||||
|
||||
var cosmo = new Cosmopolite({}, null, randstring());
|
||||
throws(
|
||||
cosmo.getMessages.bind(undefined, subject),
|
||||
'getMessages before subscribe fails');
|
||||
cosmo.subscribe(subject);
|
||||
// Verify that we can call getMessages immediately after subscribe
|
||||
cosmo.getMessages(subject);
|
||||
ok(true, 'getMessages after subscribe succeeds');
|
||||
|
||||
cosmo.shutdown();
|
||||
});
|
||||
|
||||
asyncTest('subscribe barrier', function() {
|
||||
expect(3);
|
||||
|
||||
var subject = randstring();
|
||||
var message = randstring();
|
||||
|
||||
var cosmo = new Cosmopolite({}, null, randstring());
|
||||
|
||||
cosmo.sendMessage(subject, message).then(function() {
|
||||
cosmo.subscribe(subject, -1).then(function() {
|
||||
// We are validating that the message event generated by the subscribe
|
||||
// call has already been processed by the time this promise fires
|
||||
equal(cosmo.getMessages(subject).length, 1, 'one message');
|
||||
equal(cosmo.getMessages(subject)[0]['subject']['name'], subject, 'subject matches');
|
||||
equal(cosmo.getMessages(subject)[0]['message'], message, 'message matches');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest('resubscribe', function() {
|
||||
expect(4);
|
||||
|
||||
var subject = randstring();
|
||||
var message = randstring();
|
||||
|
||||
var cosmo = new Cosmopolite({}, null, randstring());
|
||||
|
||||
cosmo.sendMessage(subject, message).then(function() {
|
||||
cosmo.subscribe(subject).then(function() {
|
||||
equal(cosmo.getMessages(subject).length, 0, 'zero messages');
|
||||
cosmo.subscribe(subject, -1).then(function() {
|
||||
var messages = cosmo.getMessages(subject);
|
||||
equal(messages.length, 1, 'one message');
|
||||
equal(messages[0]['subject']['name'], subject, 'subject matches');
|
||||
equal(messages[0]['message'], message, 'message matches');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest('Message ordering', function() {
|
||||
expect(5);
|
||||
|
||||
var subject = randstring();
|
||||
var messages = [ 'A', 'B', 'C', 'D', 'E', 'F' ];
|
||||
var keys = [ null, 'X', 'X', null, null, null ];
|
||||
|
||||
var cosmo = new Cosmopolite({}, null, randstring());
|
||||
|
||||
var sendNextMessage = function() {
|
||||
if (messages.length) {
|
||||
cosmo.sendMessage(subject, messages.shift(), keys.shift()).then(sendNextMessage);
|
||||
} else {
|
||||
cosmo.subscribe(subject, 1).then(function() {
|
||||
cosmo.subscribe(subject, 0, null, ['X']).then(function() {
|
||||
cosmo.subscribe(subject, 2).then(function() {
|
||||
var fetched = cosmo.getMessages(subject);
|
||||
equal(fetched.length, 3, 'three messages');
|
||||
equal(fetched[0]['message'], 'C', 'message 0: C matches');
|
||||
equal(fetched[1]['message'], 'E', 'message 1: E matches');
|
||||
equal(fetched[2]['message'], 'F', 'message 2: F matches');
|
||||
equal(cosmo.getKeyMessage(subject, 'X')['message'], 'C', 'key X matches');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
sendNextMessage();
|
||||
});
|
||||
|
||||
asyncTest('Reconnect channel', function() {
|
||||
expect(2);
|
||||
|
||||
var subject = randstring();
|
||||
var message = randstring();
|
||||
|
||||
var callbacks = {
|
||||
'onMessage': function(msg) {
|
||||
equal(msg['subject']['name'], subject, 'subject matches');
|
||||
equal(msg['message'], message, 'message matches');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
},
|
||||
};
|
||||
|
||||
var cosmo = new Cosmopolite(callbacks, null, randstring());
|
||||
cosmo.subscribe(subject, 0).then(function() {
|
||||
// Reach inside to forcefully close the socket
|
||||
cosmo.socket_.close();
|
||||
cosmo.sendMessage(subject, message);
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest('subscribe ACL', function() {
|
||||
expect(2);
|
||||
|
||||
var subject = randstring();
|
||||
|
||||
logout(function() {
|
||||
var tempCosmo = new Cosmopolite({}, null, randstring());
|
||||
tempCosmo.getProfile().then(function(tempProfile) {
|
||||
tempCosmo.shutdown();
|
||||
|
||||
var cosmo = new Cosmopolite({}, null, randstring());
|
||||
cosmo.getProfile().then(function(profile) {
|
||||
cosmo.subscribe({
|
||||
'name': subject,
|
||||
'readable_only_by': profile,
|
||||
}).then(function() {
|
||||
ok(true, 'correct ACL succeeds');
|
||||
|
||||
cosmo.subscribe({
|
||||
'name': subject,
|
||||
'readable_only_by': tempProfile,
|
||||
}).then(null, function() {
|
||||
ok(true, 'bad ACL fails');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest('sendMessage ACL', function() {
|
||||
expect(2);
|
||||
|
||||
var subject = randstring();
|
||||
var message = randstring();
|
||||
|
||||
logout(function() {
|
||||
var tempCosmo = new Cosmopolite({}, null, randstring());
|
||||
tempCosmo.getProfile().then(function(tempProfile) {
|
||||
tempCosmo.shutdown();
|
||||
|
||||
var cosmo = new Cosmopolite({}, null, randstring());
|
||||
cosmo.getProfile().then(function(profile) {
|
||||
cosmo.sendMessage({
|
||||
'name': subject,
|
||||
'writable_only_by': profile,
|
||||
}, message).then(function() {
|
||||
ok(true, 'correct ACL succeeds');
|
||||
|
||||
cosmo.sendMessage({
|
||||
'name': subject,
|
||||
'writable_only_by': tempProfile,
|
||||
}, message).then(null, function() {
|
||||
ok(true, 'bad ACL fails');
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module('dev_appserver only');
|
||||
|
||||
asyncTest('Login', function() {
|
||||
expect(3);
|
||||
|
||||
var anonymousProfile;
|
||||
|
||||
logout(function() {
|
||||
var callbacks = {
|
||||
'onLogout': function(login_url) {
|
||||
ok(true, 'onLogout fired');
|
||||
anonymousProfile = cosmo.currentProfile();
|
||||
// Entirely magic URL that sets the login cookie and redirects.
|
||||
window.open('/_ah/login?email=test%40example.com&action=Login&continue=/cosmopolite/static/login_complete.html');
|
||||
},
|
||||
'onLogin': function(login_url) {
|
||||
ok(true, 'onLogin fired');
|
||||
notEqual(anonymousProfile, cosmo.currentProfile(), 'profile changed');
|
||||
cosmo.shutdown();
|
||||
logout();
|
||||
start();
|
||||
},
|
||||
};
|
||||
var cosmo = new Cosmopolite(callbacks, null, randstring());
|
||||
});
|
||||
});
|
||||
|
||||
asyncTest('Profile merge', function() {
|
||||
expect(6);
|
||||
|
||||
var subject = randstring();
|
||||
var message = randstring();
|
||||
|
||||
var messages = 0;
|
||||
|
||||
logout(function() {
|
||||
var callbacks = {
|
||||
'onMessage': function(msg) {
|
||||
messages++;
|
||||
equal(msg['subject']['name'], subject,
|
||||
'message #' + messages + ': subject matches');
|
||||
equal(msg['message'], message,
|
||||
'message #' + messages + ': message matches');
|
||||
equal(msg['sender'], cosmo.currentProfile(),
|
||||
'message #' + messages + ': profile matches');
|
||||
if (messages == 1) {
|
||||
cosmo.unsubscribe(subject);
|
||||
// Entirely magic URL that sets the login cookie and redirects.
|
||||
window.open('/_ah/login?email=test%40example.com&action=Login&continue=/cosmopolite/static/login_complete.html');
|
||||
}
|
||||
if (messages == 2) {
|
||||
cosmo.shutdown();
|
||||
start();
|
||||
}
|
||||
},
|
||||
'onLogin': function(logout_url) {
|
||||
cosmo.subscribe(subject, -1);
|
||||
},
|
||||
};
|
||||
var cosmo = new Cosmopolite(callbacks, null, randstring());
|
||||
cosmo.sendMessage(subject, message);
|
||||
cosmo.subscribe(subject, -1);
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
curl \
|
||||
--silent \
|
||||
--data compilation_level=ADVANCED_OPTIMIZATIONS \
|
||||
--data output_format=json \
|
||||
--data output_info=errors \
|
||||
--data output_info=warnings \
|
||||
--data language=ECMASCRIPT5 \
|
||||
--data warning_level=verbose \
|
||||
--data externs_url=https://closure-compiler.googlecode.com/git/contrib/externs/jquery-1.8.js \
|
||||
--data-urlencode "js_code@cosmopolite.js" \
|
||||
http://closure-compiler.appspot.com/compile
|
||||
|
||||
echo
|
||||
328
stylesheets/styles.css
Normal file
328
stylesheets/styles.css
Normal file
@@ -0,0 +1,328 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Arvo:400,700,400italic);
|
||||
|
||||
/* MeyerWeb Reset */
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
|
||||
/* Base text styles */
|
||||
|
||||
body {
|
||||
padding:10px 50px 0 0;
|
||||
font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #232323;
|
||||
background-color: #FBFAF7;
|
||||
margin: 0;
|
||||
line-height: 1.8em;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color:#232323;
|
||||
margin:36px 0 10px;
|
||||
}
|
||||
|
||||
p, ul, ol, table, dl {
|
||||
margin:0 0 22px;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-family: Arvo, Monaco, serif;
|
||||
line-height:1.3;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h1,h2, h3 {
|
||||
display: block;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h4, h5, h6 {
|
||||
font-family: Arvo, Monaco, serif;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
a {
|
||||
color:#C30000;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a small {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight:700;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: inside;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: decimal inside;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0;
|
||||
padding: 0 0 0 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
dl, dt, dd, dl p {
|
||||
font-color: #444;
|
||||
}
|
||||
|
||||
dl dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
padding-left: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
dl p {
|
||||
padding-left: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
hr {
|
||||
border:0;
|
||||
background:#ccc;
|
||||
height:1px;
|
||||
margin:0 0 24px;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
max-width: 650px;
|
||||
padding: 5px;
|
||||
margin: 10px 0 32px 0;
|
||||
}
|
||||
|
||||
p img {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
|
||||
code, pre {
|
||||
font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;
|
||||
color:#000;
|
||||
font-size:14px;
|
||||
}
|
||||
|
||||
comment {
|
||||
color:#00f;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 4px 12px;
|
||||
background: #FDFEFB;
|
||||
border-radius:4px;
|
||||
border:1px solid #D7D8C8;
|
||||
overflow: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* Tables */
|
||||
|
||||
table {
|
||||
width:100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #ccc;
|
||||
margin-bottom: 32px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
font-family: 'Arvo', Helvetica, Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
padding: 10px;
|
||||
background: #232323;
|
||||
color: #FDFEFB;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 10px;
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
|
||||
/* Wrapper */
|
||||
.wrapper {
|
||||
width:960px;
|
||||
}
|
||||
|
||||
|
||||
/* Header */
|
||||
|
||||
header {
|
||||
background-color: #171717;
|
||||
color: #FDFDFB;
|
||||
width:170px;
|
||||
float:left;
|
||||
position:fixed;
|
||||
border: 1px solid #000;
|
||||
-webkit-border-top-right-radius: 4px;
|
||||
-webkit-border-bottom-right-radius: 4px;
|
||||
-moz-border-radius-topright: 4px;
|
||||
-moz-border-radius-bottomright: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
padding: 34px 25px 22px 50px;
|
||||
margin: 30px 25px 0 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
p.header {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
h1.header {
|
||||
font-family: Arvo, sans-serif;
|
||||
font-size: 30px;
|
||||
font-weight: 300;
|
||||
line-height: 1.3em;
|
||||
border-bottom: none;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
||||
h1.header, a.header, a.name, header a{
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
a.header {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a.name {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
header ul {
|
||||
list-style:none;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
header li {
|
||||
list-style-type: none;
|
||||
width:132px;
|
||||
height:15px;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1em;
|
||||
padding: 6px 6px 6px 7px;
|
||||
|
||||
background: #AF0011;
|
||||
background: -moz-linear-gradient(top, #AF0011 0%, #820011 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));
|
||||
background: -webkit-linear-gradient(top, #AF0011 0%,#820011 100%);
|
||||
background: -o-linear-gradient(top, #AF0011 0%,#820011 100%);
|
||||
background: -ms-linear-gradient(top, #AF0011 0%,#820011 100%);
|
||||
background: linear-gradient(top, #AF0011 0%,#820011 100%);
|
||||
|
||||
border-radius:4px;
|
||||
border:1px solid #0D0D0D;
|
||||
|
||||
-webkit-box-shadow: inset 0px 1px 1px 0 rgba(233,2,38, 1);
|
||||
box-shadow: inset 0px 1px 1px 0 rgba(233,2,38, 1);
|
||||
|
||||
}
|
||||
|
||||
header li:hover {
|
||||
background: #C3001D;
|
||||
background: -moz-linear-gradient(top, #C3001D 0%, #950119 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));
|
||||
background: -webkit-linear-gradient(top, #C3001D 0%,#950119 100%);
|
||||
background: -o-linear-gradient(top, #C3001D 0%,#950119 100%);
|
||||
background: -ms-linear-gradient(top, #C3001D 0%,#950119 100%);
|
||||
background: linear-gradient(top, #C3001D 0%,#950119 100%);
|
||||
}
|
||||
|
||||
a.buttons {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background: url(../images/arrow-down.png) no-repeat;
|
||||
font-weight: normal;
|
||||
text-shadow: rgba(0, 0, 0, 0.4) 0 -1px 0;
|
||||
padding: 2px 2px 2px 22px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
a.github {
|
||||
background: url(../images/octocat-small.png) no-repeat 1px;
|
||||
}
|
||||
|
||||
a.forum {
|
||||
background: url(../images/forum.png) no-repeat 1px;
|
||||
}
|
||||
|
||||
a.buttons:hover {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
/* Section - for main page content */
|
||||
|
||||
section {
|
||||
width:650px;
|
||||
float:right;
|
||||
padding-bottom:50px;
|
||||
}
|
||||
130
tutorial.html
Normal file
130
tutorial.html
Normal file
@@ -0,0 +1,130 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||
<title>Cosmopolite Tutorial</title>
|
||||
|
||||
<link rel="stylesheet" href="stylesheets/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<h1 class="header"><a href="/">Cosmopolite</a></h1>
|
||||
<p class="header">Client/server publish/subscribe, presence and key/value storage for AppEngine</p>
|
||||
|
||||
<ul>
|
||||
<li class="download"><a class="buttons" href="https://github.com/flamingcowtv/cosmopolite/zipball/master" onclick="return trackOutboundLink(this.href);">Download ZIP</a></li>
|
||||
<li class="download"><a class="buttons" href="https://github.com/flamingcowtv/cosmopolite/tarball/master" onclick="return trackOutboundLink(this.href);">Download TAR</a></li>
|
||||
<li><a class="buttons github" href="https://github.com/flamingcowtv/cosmopolite" onclick="return trackOutboundLink(this.href);">View On GitHub</a></li>
|
||||
<li><a class="buttons forum" href="https://groups.google.com/a/cosmopolite.org/forum/#!forum/discuss" onclick="return trackOutboundLink(this.href);">Mailing list</a></li>
|
||||
</ul>
|
||||
|
||||
</header>
|
||||
<section>
|
||||
<h1>
|
||||
<a name="cosmopolite" class="anchor" href="#cosmopolite"><span class="octicon octicon-link"></span></a>Cosmopolite Tutorial</h1>
|
||||
|
||||
<h3>Test cosmopolite</h3>
|
||||
<ol>
|
||||
<li>Load the cosmopolite playground <a href="https://playground.cosmopolite.org/cosmopolite/static/debug.html" target="_blank">debug page</a>.
|
||||
<li>Subscribe to a subject <i>testsubject</i></li>
|
||||
<li>Send a message to that subject; it should appear in the message list at right.</li>
|
||||
</ol>
|
||||
|
||||
<h3>Send a message to a subject</h3>
|
||||
<ol>
|
||||
<li>Load the <a href="https://playground.cosmopolite.org/cosmopolite/static/debug.html" target="_blank">debug page</a>.
|
||||
<li>Subscribe to <i>testsubject</i></li>
|
||||
<li>Load <a href="https://playground.cosmopolite.org/cosmopolite-tutorial/1.html" target="_blank">tutorial page #1</a></li>
|
||||
<li>Click <b>Execute JavaScript</b>, then click OK on the popup</li>
|
||||
<li>The debug page should show the message from the tutorial page</li>
|
||||
</ol>
|
||||
|
||||
<h3>Subscribe to a subject</h3>
|
||||
<ol>
|
||||
<li>Load <a href="https://playground.cosmopolite.org/cosmopolite-tutorial/2.html" target="_blank">tutorial page #2</a></li>
|
||||
<li>Click <b>Execute JavaScript</b>, then click OK on the popup</li>
|
||||
<li>Use the <a href="https://playground.cosmopolite.org/cosmopolite/static/debug.html" target="_blank">debug page</a> to send a message to <i>testsubject</i></li>
|
||||
<li>The tutorial page should generate an alert popup</li>
|
||||
</ol>
|
||||
|
||||
<h3>Key/value store</h3>
|
||||
<ol>
|
||||
<li>Cosmopolite can be used as a key/value store, with historical data and update notification</li>
|
||||
<li>When subscribing to a subject, you can request the most recent message for that subject</li>
|
||||
<li>Load <a href="https://playground.cosmopolite.org/cosmopolite-tutorial/3.html" target="_blank">tutorial page #3</a></li>
|
||||
<li>Click <b>Execute JavaScript</b>, then click OK on the popup</li>
|
||||
<li>The tutorial page should generate an alert popup showing the last message sent</li>
|
||||
</ol>
|
||||
|
||||
<h3>Pin a value</h3>
|
||||
<ol>
|
||||
<li>Pins publish a message temporarily, until the source conncetion disconnects. This can be used for presence information</li>
|
||||
<li>Load the <a href="https://playground.cosmopolite.org/cosmopolite/static/debug.html" target="_blank">debug page</a>.
|
||||
<li>Subscribe to <i>testsubject</i></li>
|
||||
<li>Load <a href="https://playground.cosmopolite.org/cosmopolite-tutorial/4.html" target="_blank">tutorial page #4</a></li>
|
||||
<li>Click <b>Execute JavaScript</b>, then click OK on the popup</li>
|
||||
<li>Watch the debug page; you should see the pin.</li>
|
||||
<li>Close tutorial page #4; the pin disappears from the debug page.</li>
|
||||
</ol>
|
||||
|
||||
<h3>Set up a local instance for further testing</h3>
|
||||
|
||||
<ol>
|
||||
<li>Download and install the <a href="https://developers.google.com/appengine/downloads">Python AppEngine SDK</a></li>
|
||||
<li>Create an application in your SDK client</li>
|
||||
<li>Download cosmopolite via the links at left. Unpack it into your local application directory and rename the newly-created subdirectory to "cosmopolite"</li>
|
||||
<li>Create an <a href="https://developers.google.com/appengine/docs/python/config/appconfig">app.yaml</a> file for your application.</li>
|
||||
<li>Add a cosmopolite include to your app.yaml file:
|
||||
<pre>includes:
|
||||
- cosmopolite
|
||||
|
||||
inbound_services:
|
||||
- channel_presence</pre></li>
|
||||
<li>Start the application in your SDK client</li>
|
||||
<li>Create a test.html page at the top level of your application:
|
||||
<pre><html>
|
||||
<head>
|
||||
<script src="/cosmopolite/static/cosmopolite.js" charset="UTF-8"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
// Subsequent script examples go here
|
||||
</script>
|
||||
</body>
|
||||
</html></pre></li>
|
||||
<li>Add a mapping to app.yaml:
|
||||
<pre>handlers:
|
||||
- url: /test.html
|
||||
static_files: test.html
|
||||
upload: test.html</pre></li>
|
||||
<li>Remove the mapping for main.app from app.yaml (if using the autogenerated file)</li>
|
||||
<li>Retry the examples above using your new test page and your local debug page</li>
|
||||
</ol>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-37845853-2', 'auto');
|
||||
ga('require', 'displayfeatures');
|
||||
ga('send', 'pageview');
|
||||
|
||||
var trackOutboundLink = function(url) {
|
||||
ga('send', 'event', 'outbound', 'click', url, {
|
||||
'hitCallback': function () {
|
||||
document.location = url;
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user