Exchanging data between Python Telethon library and Node.js - python

I faced such a problem: in my small test app I have simple node.js (express) server and python script, which allows me to interact with Telegram API using Telethon library. In my scenario I have to provide my python script a phone number and a password. These data is asked in the input mode, so I can't figure out, how am I able to:
Accept input request from python script on node.js side;
Provide these credentials by node.js and pass them back via python's input;
Repeat this actions several times to gain all info I need.
These are my test files:
file.py
import os
from telethon import TelegramClient
api_id = 12345
api_hash = 'hash'
session = 'testing'
proxy = None
client = TelegramClient(session, api_id, api_hash, proxy=proxy).start()
def get_ids_list():
ids_dict = {}
async def do_fetch():
async for dialog in client.iter_dialogs():
ids_dict[dialog.name] = dialog.id
with client:
client.loop.run_until_complete(do_fetch())
return ids_dict
def print_ids_list():
async def do_print():
async for dialog in client.iter_dialogs():
print(dialog.name, 'has ID', dialog.id)
with client:
client.loop.run_until_complete(do_print())
print_ids_list()
When this script is run, I'm prompted the following input:
Please enter your phone (or bot token):
And this is my index.js, in which I want to pass prepared data to this input:
import express from "express";
import { spawn } from "child_process";
const app = express();
const port = 3000;
app.get("/", (req, res) => {
var myPythonScript = "path/to/file.py";
var pythonExecutable = "python";
var uint8arrayToString = function (data) {
return String.fromCharCode.apply(null, data);
};
const scriptExecution = spawn(pythonExecutable, [myPythonScript]);
scriptExecution.stdout.on("data", (data) => {
console.log(uint8arrayToString(data));
});
scriptExecution.stderr.on("data", (data) => {
console.log(uint8arrayToString(data));
});
scriptExecution.on("exit", (code) => {
console.log("Process quit with code : " + code);
});
});
app.listen(port, () =>
console.log(`Example app listening on port ${port}!`)
);
So, is there a way to solve this case?

Using with client is equivalent to client.start(), and as stated:
By default, this method will be interactive (asking for user input if needed), and will handle 2FA if enabled too.
You need to instead do what it does manually, remove with block, and make a function to authenticate (or confirm if already authorized).
for a minimal func example:
....
if not client.is_connected():
await client.connect()
if not await client.is_user_authorized():
await client.send_code_request(phone)
# get the code somehow, and in a reasonably fast way
try:
await client.sign_in(phone, code)
except telethon.errors.SessionPasswordNeededError:
'''
Retry with password.
note: it's necessary to make it error once
even if you know you have a pass, iow don't pass password the first time.
'''
await client.sign_in(phone, code, password=password)
return client
else:
return client
Dealing with the steps sequentially and interactivly while waiting for needed params to login successfully while also keeping in mind you have time limit until code expires is your task to handle any of their undefined behavior dependant on your usecase.

Related

how to send video stream using webrtc

I'm new to webrtc,i liked to make simple application where client send video and audio stream to server using webrtc and from server i will use the video frame for detecting object in the video using opencv, i have implemented simple server side code using docs of aiortc package,but i was stuck,because the on_track never called i dont know whats wrong with my code
Client code
const { RTCPeerConnection, RTCSessionDescription } = window;
socket = io()
const peerConnection = new RTCPeerConnection()
const offerAsync = peerConnection.createOffer();
offerAsync.then(async offer=>{
await peerConnection.setLocalDescription(new RTCSessionDescription(offer));
socket.emit("connect_to_server", {
offer
});
})
socket.on("connect_to_server_response",async function(data){
navigator.mediaDevices.getUserMedia({
video:true,
audio:true
}).then(async stream=>{
const video = document.getElementById("video")
await peerConnection.setRemoteDescription(
data
);
console.log(data)
stream.getTracks().forEach(track => {
console.log("this is track")
peerConnection.addTrack(track, stream)
});
console.log(video)
video.srcObject = stream
video.play()
}).catch(err=>{
console.log(err)
console.log("something went wrong please connect administrator, error code = [100]")
})
})
console.log("working");
Server code
async def res(data):
offer = RTCSessionDescription(sdp=data["offer"]["sdp"], type=data["offer"]["type"])
pc = RTCPeerConnection()
#pc.on("track")
def on_track(track):
print("on Track")
print(track.kind)
await pc.setRemoteDescription(offer)
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
emit("connect_to_server_response", {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type})
#socketio.on("connect_to_server")
def connect_to_server(data):
asyncio.set_event_loop(asyncio.SelectorEventLoop())
asyncio.get_event_loop().run_until_complete(res(data))
print(data)
print(request.sid)
print("new user joint")
It looks like you are exchanging the SDP messages fine and in the right order.
Your issue is because you are never exchanging ICE candidates. In order to establish a connection, you will need to emit ice candidate messages from the js client to the python client as well as from the python client to the js client.
You need to define an event handler for RTCPeerConnection.onicecandidate (this will start getting fired once you call createOffer) on both sides and emit these candidates that you're receiving. Then you have to RTCPeerConnection.addIceCandidate once you receive the candidates.
Once you successfully do that, you should be able to get the on_track event.
Check out these links for reference:
What are ICE Candidates and how do the peer connection choose between them?
RTCIceCandidate
Simple webrtc workflow sample code

How to use phone_code parameter in pyrogram framework(python)

If you know pyrogram is a Telegram MTProto API Framework for Python
Which You can give it a phone number to create a user account as a bot:
see the doc
When you give it a phone number, telegram will send you a phone code to authentication and you can enter the phone code manually.
also you can give your script the phone code automatically by phone_code parameter in Client class:
see the client doc
I send my phone number from node.js script to python script and telegram send me a phone code to verify.
But when i send the phone code, it doesn't work correctly. actually it send the phone code again
What is my mistake?
my node.js code:
if(command == 'run cli'){
var spawn = require("child_process").spawn;
var process = spawn('python3', ["python/cli.py"]);
process.stdout.on('data', function(data) {
console.log(data.toString());
});
}
else if(command.substr(0, 9) == 'send code'){
var code = command.substr(10).trim();
var spawn = require("child_process").spawn;
var process = spawn('python3', ["python/cli_enter_code.py", code]);
process.stdout.on('data', function(data) {
console.log(data.toString());
});
}
cli.py file:
from pyrogram import Client
from pyrogram.raw import functions
api_id = someNumber
api_hash = "someHash"
with Client("my_account", api_id, api_hash, phone_number="my phone number") as app:
print('Bot is online...')
app.run()
cli_enter_code.py file:
import sys
code = sys.argv[1]
from pyrogram import Client
from pyrogram.raw import functions
api_id = someNumber
api_hash = "someHash"
with Client("my_account", api_id, api_hash, phone_number='my phone number', phone_code=code) as app:
print('Bot is online...')
app.run()

Why is the python client not receiving SSE events?

I am have a python client listening to SSE events from a server with node.js API
The flow is I sent an event to the node.js API through call_notification.py and run seevents.py in loop using run.sh(see below)
However I don't see that python client is receiving this SSE event? any guidance on why is that?
call_notification.py
import requests
input_json = {'BATS':'678910','root_version':'12A12'}
url = 'http://company.com/api/root_event_notification?params=%s'%input_json
response = requests.get(url)
print response.text
node.js API
app.get("/api/root_event_notification", (req, res, next) => {
console.log(req.query.params)
var events = require('events');
var eventEmitter = new events.EventEmitter();
//Create an event handler:
var myEventHandler = function () {
console.log('new_root_announced!');
res.status(200).json({
message: "New root build released!",
posts: req.query.params
});
}
seevents.py (python client listening to SSE events)
import json
import pprint
import sseclient
def with_urllib3(url):
"""Get a streaming response for the given event feed using urllib3."""
import urllib3
http = urllib3.PoolManager()
return http.request('GET', url, preload_content=False)
def with_requests(url):
"""Get a streaming response for the given event feed using requests."""
import requests
return requests.get(url, stream=True)
url = 'http://company.com/api/root_event_notification'
response = with_urllib3(url) # or with_requests(url)
client = sseclient.SSEClient(response)
#print client.events()
for event in client.events():
print "inside"
pprint.pprint(json.loads(event.data))
run.sh
#!/bin/sh
while [ /usr/bin/true ]
do
echo "Running sseevents.py"
python sseevents.py 2>&1 | tee -a sseevents.log.txt
echo "sleeping for 30 sec"
sleep 30
done
OUTPUT:-
Run call_notification.py on Terminal
node.js API OUTPUT
new_root_announced!
{'root_version': 'ABCD', 'BATS': '143'}
./run.sh --> DON'T SEE ABOVE EVENT below
Running sseevents.py
sleeping for 30 sec
Running sseevents.py
sleeping for 30 sec
Running sseevents.py
sleeping for 30 sec
Very short answer to you question:
The server code is not sending a SSE message back to the client.
Why? Because you need to follow the SSE format.
According to JASON BUTZ in Server-Sent Events With Node
You should send a Connection: keep-alive header to ensure the client keeps the connection open as well. A Cache-Control header should be sent with the value no-cache to discourage the data being cached. Finally, the Content-Type needs to be set to text/event-stream.
With all of that done a newline (\n) should be sent to the client and then the events can be sent. Events must be sent as strings, but what is in that string doesn’t matter. JSON strings are perfectly fine.
Event data must be sent in the format "data: <DATA TO SEND HERE>\n".
It’s important to note that at the end of each line should be a newline character. To signify the end of an event an extra newline character needs to be added as well.
Multiple data lines are perfectly fine.
Long answer to your question:
According to Eric Bidelman in html5rocks.com:
When communicating using SSEs, a server can push data to your app whenever it wants, without the need to make an initial request. In other words, updates can be streamed from server to client as they happen.
But, in order for this to happen, the client has to "start" by asking for it AND prepare to receive a stream of messages (when they happen).
The "start" is done by calling a SSE API endpoint (in your case, calling the Node.js API code).
The preparation is done by preparing to handle a stream of asynchronous messages.
SSEs open a single unidirectional channel between server and client.*
* The emphasis is mine
This means that the server has a "direct" channel to the client. It is not intended to be "started" (opened) by some other process/code that is not "the client" code.
Assuming from OP comments...
Expected behavior (verbose)
A client Alice calls the API endpoint with params {name: "Alice"}, nothing (visible) happens.
...then a client Bob calls the API endpoint with params {name: "Bob"}, client Alice receives a SSE with payload {name: "Bob", says: "Hi"}.
...then a client Carol calls the API endpoint with params {name: "Carol"}, clients Alice AND Bob each one receives a SSE with payload {name: "Carol", says: "Hi"}.
...and so on. Every time a new client calls the API endpoint with params, every other client who has a channel "open" will receive a SSE with the new "Hi" payload.
...and then client Bob "disconnects" from the server, client Alice, client Carol and all the clients that have a channel "open" will receive a SSE with payload {name: "Bob", says: "Bye"}.
...and so on. Every time an old client "disconnects" from the server, every other client who has a channel "open" will receive a SSE with the new "Bye" payload.
Abstracted behavior
Each new client that asks to "open" a channel sending some params or an old client "disconnects" from the server, they cause and event in the server.
Every time such an event happens in the server, the server sends a SSE message with the params and a message as payload to all the "open" channels.
Note on blocking Each client with an "open" channel will be "stuck" in an infinite waiting loop for events to happen. It is client design responsibility to use "threading" code techniques to avoid blocking.
Code
Your Python client should "ask" to start the single unidirectional channel AND keep waiting UNTIL the channel is closed. Should not end and start all over again with a different channel. It should keep the same channel open.
From the network perspective, it will be like a "long" response that does not end (until the SSE messaging is over). The response just "keeps coming and coming".
Your Python client code does that. I noted it is the exact sample code used from sseclient-py library.
Client code for Python 3.4
To include the parameters you want to send to the server, use some code from the Requests library docs/#passing-parameters-in-urls.
So, mixing those samples we end up with the following code as your Python 3.4 client:
import json
import pprint
import requests
import sseclient # sseclient-py
# change the name for each client
input_json = {'name':'Alice'}
#input_json = {'name':'Bob'}
#input_json = {'name':'Carol'}
url = 'http://company.com/api/root_event_notification'
stream_response = requests.get(url, params=input_json, stream=True)
client = sseclient.SSEClient(stream_response)
# Loop forever (while connection "open")
for event in client.events():
print ("got a new event from server")
pprint.pprint(event.data)
Client code for Python 2.7
To include the parameters you want to send to the server, encode them in the URL as query parameters using urllib.urlencode() library.
Make the http request with urllib3.PoolManager().request() so you will end up with a stream response.
Note that the sseclient library returns event data as unicode string. To convert back the JSON object to python object (with python strings) use byteify, a recursive custom function ( thanks to Mark Amery ).
Use the following code as your Python 2.7 client:
import json
import pprint
import urllib
import urllib3
import sseclient # sseclient-py
# Function that returns byte strings instead of unicode strings
# Thanks to:
# [Mark Amery](https://stackoverflow.com/users/1709587/mark-amery)
def byteify(input):
if isinstance(input, dict):
return {byteify(key): byteify(value)
for key, value in input.iteritems()}
elif isinstance(input, list):
return [byteify(element) for element in input]
elif isinstance(input, unicode):
return input.encode('utf-8')
else:
return input
# change the name for each client
input_json = {'name':'Alice'}
#input_json = {'name':'Bob'}
#input_json = {'name':'Carol'}
base_url = 'http://localhost:3000/api/root_event_notification'
url = base_url + '?' + urllib.urlencode(input_json)
http = urllib3.PoolManager()
stream_response = http.request('GET', url, preload_content=False)
client = sseclient.SSEClient(stream_response)
# Loop forever (while connection "open")
for event in client.events():
print ("got a new event from server")
pprint.pprint(byteify(json.loads(event.data)))
Now, the server code should:
emit an inside-server 'hello' event so other clients listen to the event
"open" the channel
Register to listen for all possible inside-server events to happen (this means, keeping the channel "open" and not sending anything between messages, just keeping the channel "open").
This includes to emit an inside-server 'goodbye' event so other clients listen to the event WHEN channel is closed by the client/network (and finally "wrap up").
Use the following Node.js API code:
var EventEmitter = require('events').EventEmitter;
var myEmitter = new EventEmitter;
function registerEventHandlers(req, res) {
// Save received parameters
const myParams = req.query;
// Define function that adds "Hi" and send a SSE formated message
const sayHi = function(params) {
params['says'] = "Hi";
let payloadString = JSON.stringify(params);
res.write(`data: ${payloadString}\n\n`);
}
// Define function that adds "Bye" and send a SSE formated message
const sayBye = function(params) {
params['says'] = "Bye";
let payloadString = JSON.stringify(params);
res.write(`data: ${payloadString}\n\n`);
}
// Register what to do when inside-server 'hello' event happens
myEmitter.on('hello', sayHi);
// Register what to do when inside-server 'goodbye' event happens
myEmitter.on('goodbye', sayBye);
// Register what to do when this channel closes
req.on('close', () => {
// Emit a server 'goodbye' event with "saved" params
myEmitter.emit('goodbye', myParams);
// Unregister this particular client listener functions
myEmitter.off('hello', sayHi);
myEmitter.off('goodbye', sayBye);
console.log("<- close ", req.query);
});
}
app.get("/api/root_event_notification", (req, res, next) => {
console.log("open -> ", req.query);
// Emit a inside-server 'hello' event with the received params
myEmitter.emit('hello', req.query);
// SSE Setup
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
res.write('\n');
// Register what to do when possible inside-server events happen
registerEventHandlers(req, res);
// Code execution ends here but channel stays open
// Event handlers will use the open channel when inside-server events happen
})
...continue quoting Eric Bidelman in html5rocks.com:
Sending an event stream from the source is a matter of constructing a plaintext response, served with a text/event-stream Content-Type, that follows the SSE format. In its basic form, the response should contain a "data:" line, followed by your message, followed by two "\n" characters to end the stream
In the client code, the sseclient-py library takes care of interpreting the SSE format so every time the two "\n" characters arrive, the library "iterates" a new "iterable" object (a new event) that has the data property with the message sent from the server.
This is how I tested the code
Started server with Node.js API code
Run a client with only the "Alice" line uncommented (Nothing is seen on this client console yet).
Run a second client with only "Bob" line uncommented. The console of the first client "Alice" shows: Bob saying "Hi" (Nothing is seen on Bob's client console yet).
Run a third client with only "Carol" line uncommented. Alice's and Bob's consoles show: Carol saying "Hi" (Nothing is seen on Carol's client console yet).
Stop/kill Bob's client. Alice's and Carol's consoles show: Bob saying "Bye".
So, code works OK :)

Twilio Programmable Voice call immediately completes

I am using the new Twilio Programmable Voice SDK with Swift and Python. I have begun with the respective quick starter projects, and for the most part things work. I'm able to get a valid access token, I'm able to successfully create a call, and I am even able to receive the call. The problem is on the caller side of the house.
When I try to make a call via the Swift SDK the call is disconnected before it ever starts to ring on the other end.
I have read in the Twilio docs that the client.calls.create function will immediately return a status of complete if you do not handle the status_callback event. I have tried to add this but whenever I do I get an error saying the the key status_callback is not a valid parameter for the client.calls.create function. Also, I cannot find any examples anywhere of actually how to handle the call status.
My question is what in the wold am I doing wrong here? Any help would be greatly appreciated.
Here is my Python code
#app.route('/outgoing', methods=['GET', 'POST'])
def outgoing():
account_sid = os.environ.get("ACCOUNT_SID", ACCOUNT_SID)
api_key = os.environ.get("API_KEY", API_KEY)
api_key_secret = os.environ.get("API_KEY_SECRET", API_KEY_SECRET)
from_number = [HARD_CODED_PHONE_NUMBER_FOR_CALLER]
to_number = [HARD_CODED_PHONE_NUMBER_FOR_RECIPIENT]
client = Client(api_key, api_key_secret, account_sid)
call = client.calls.create(url='http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient', to=to_number, from_=from_number)
# return str(call.sid)
resp = twilio.twiml.Response()
resp.say("Thank you for calling");
return str(resp)
Here is my relevant iOS code. Please bear in mind that this is NOT my full source. I have only provided what should be necessary in this event. My full source does include handling the registry and invite delegates. I also did not include my source that shows/hides my active call UI as there are no problems with that. This is simply to show how I am placing a call and receiving the call complete delegate.
class VoiceManager: NSObject, PKPushRegistryDelegate, TVONotificationDelegate, TVOCallDelegate, AVAudioPlayerDelegate {
//MARK: - Singleton
static let sharedManager = VoiceManager()
//MARK: - Private Constants
private let baseURLString = [MY_WEBAPP_ENDPOINT]
private let accessTokenEndpoint = "/accessToken"
//MARK: - Private Variables
private var deviceTokenString:String?
private var callInvite: TVOCallInvite?
private var call: TVOCall?
private var status: VoiceStatus = .idle
//MARK: - Getters
private func fetchAccessToken() -> String? {
guard let accessTokenURL = URL(string: baseURLString + accessTokenEndpoint) else {
return nil
}
return try? String.init(contentsOf: accessTokenURL, encoding: .utf8)
}
func placeCall(withParameters params: VoiceParameters, completion: #escaping (_ success: Bool, _ error: VAError?) -> Void) {
if (call != nil) {
call?.disconnect()
completion(false, .phoneCallInProgress)
status = .callEnded
hideActiveCallUI()
} else {
guard let accessToken = fetchAccessToken() else {
completion(false, .phoneAccessTokenFetchFailed)
return
}
guard let paramsDict = params.toDictionary() else {
completion(false, .phoneAccessTokenFetchFailed)
return
}
playOutgoingRingtone(completion: { [weak self] in
if let strongSelf = self {
strongSelf.call = VoiceClient.sharedInstance().call(accessToken, params: [:], delegate: strongSelf) //NOTE: The params here are not necessary as the phone numbers for now are hard coded on the server
if (strongSelf.call == nil) {
strongSelf.status = .callEnded
completion(false, .phoneCallFailed)
return
} else {
strongSelf.status = .callConnecting
self?.showActiveCallUI(withParameters: params)
completion(true, nil)
}
}
})
}
}
// MARK: TVOCallDelegate
func callDidConnect(_ call: TVOCall) {
NSLog("callDidConnect:")
self.call = call
status = .inCall
routeAudioToSpeaker()
}
func callDidDisconnect(_ call: TVOCall) {
NSLog("callDidDisconnect:")
playDisconnectSound()
self.call = nil
status = .callEnded
hideActiveCallUI()
}
func call(_ call: TVOCall, didFailWithError error: Error) {
NSLog("call:didFailWithError: \(error)");
self.call = nil
status = .callEnded
hideActiveCallUI()
}
}
Twilio developer evangelist here.
Your Swift code says that your phone numbers are hard coded on the server right now. The issue, as Robert has said too, is that you are using the REST API to generate a call when you get a callback from Twilio to your /outbound endpoint.
What is actually happening is that when you generate the call in Swift on the device that starts the call for the application. Twilio then makes an HTTP request to your /outbound endpoint to see what to do with that call. So, instead of generating a new call with the REST API, you need to respond with TwiML to tell Twilio what to do with the call next.
In this case, it sounds like you are trying to dial straight onto another number. For that, you should try the following response:
#app.route('/outgoing', methods=['GET', 'POST'])
def outgoing():
from_number = [HARD_CODED_PHONE_NUMBER_FOR_CALLER]
to_number = [HARD_CODED_PHONE_NUMBER_FOR_RECIPIENT]
resp = twilio.twiml.Response()
with resp.dial(callerId=from_number) as r:
r.number(to_number)
return str(resp)
Let me know if that helps.
Note: I have also responded in the ticket you created with Twilio Support.
Please check your account debugger for the whole pile of error notifications you are getting. Here's an example:
An attempt to retrieve content from
https://voiceapp-twilio.herokuapp.com/outgoing
https://voiceapp-twilio.herokuapp.com/outgoing returned the HTTP
status code 500.
The Python web server returned an error message, which includes this:
TypeError: create() got an unexpected keyword argument
'status_events' // Werkzeug Debugger
It looks like a code error in your Python outgoing() function. Most notably, you are attempting to use the REST API to create a new call, when you should actually be returning TwiML. You should be returning TwiML that includes the Dial verb to create the outgoing call leg.

GAE Channels to multiple clients?

I'm trying to wrap my head around the channel features of Google App Engine since they don't (easily) provide websockets.
My current situation is that I have a long work (file processing) that is being executed asynchronously via a worker.
This worker update the state of the file processing in the database at every lines in order to inform the customer.
From that current perspective, a F5 will indicate the evolution of the processing.
Now I'd like to implement a live update system. Of course I could do an XHR request every 5 seconds but a live connection seems better... introducing Channels since Websockets seems out of the possibilities.
From what I understood, I can channel.send_message to one client only, not to a "room". The issue here, is that the worker that process the file does not have any information which customer is currently connected (could be one, could be ten).
I could loop over all the customer and post to each client_id, suspecting that at least one of them will get the message, but this is awfully useless and too resourceful.
I was hoping there was a better way to achieve this ? Maybe a nice alternative to Google Channels feature without having to reconfigure my whole App Engine system (like for Websockets)?
One solution I can think of, which is not the absolute ideal but would be more suited, is to manage dedicated database tables (could also be implemented in Memcache) with :
A table that contains a list of rooms
A table that contains a list of client_id connected to the room
e.g. :
Rooms (id, name)
Clients (id, room_id, client_id)
Now, instead of posting to channel.send_message(client_id, Message), one would make a wrapper like this :
def send_to_room(room, message):
# Queries are SQLAlchemy like :
room = Rooms.query.filter(Rooms.name === room).first()
if not room:
raise Something
clients = Clients.query.filter(Rooms.room_id === room.id).all()
for client in clients:
channel.send_message(client.client_id, message)
And voilà, you have a Room like implementation in Google App Engine.
The drawback of this solution is to add two tables (or equivalent) in your database.
Does someone has better?
I am assuming that the long running task is being kicked off by the client.
So before you kick off the task make a ajax request from the client to a handler similar to this one. This handler has two things returned to the client. The token param which is used by the javascript api to create a channel, and a cid param which is used to determine which client created the channel.
from google.appengine.api import channel
#ae.route("/channel")
class CreateChannel(webapp2.RequestHandler):
def get(self):
cid = str(uuid.uuid4())
token = channel.create_channel(cid)
data = {
"cid":cid,
"token":token
}
self.response.write(json.dumps(data))
Now use the channel javascript api to create a new channel
https://cloud.google.com/appengine/docs/python/channel/javascript
var onClosed = function(resp){
console.log("channel closed");
};
var onOpened = function(resp){
console.log("channel created");
};
var onmessage = function(resp){
console.log("The client received a message from the backend task");
console.log(resp);
};
var channel_promise = $.ajax({
url: "/channel",
method: "GET"
});
channel_promise.then(function(resp){
//this channel id is important you need to get it to the backend process so it knows which client to send the message to.
var client_id = resp.data.cid;
var channel = new goog.appengine.Channel(resp.data.token);
handler = {
'onopen': $scope.onOpened,
'onmessage': $scope.onMessage,
'onerror': function () {
},
'onclose': function () {
alert("channel closed.")
}
};
socket = channel.open(handler);
//onOpened is the callback function to call after channel has been created
socket.onopen = onOpened;
//onClose is the callback function to call after channel has been closed
socket.onclose = onClosed;
//onmessage is the callback function to call when receiving messages from your task queue
socket.onmessage = onMessage;
});
Now we are all set up to listen for channel messages.
So when the user clicks the button we need to kickoff the backend task.
var letsDoSomeWorkOnClick = function(){
//make sure you pass the client id with every ajax request
$.ajax({
url: "/kickoff",
method: "POST",
params: {"cid":client_id}
});
}
Now the app engine handler to start the backend task queue. I use the deffered library to do this. https://cloud.google.com/appengine/articles/deferred
#ae.route("/kickoff")
KickOffHandler(webapp2.RequestHandler):
def post(self):
cid = self.request.get("cid")
req = {}
req['cid'] = cid
task = MyLongRunningTask()
deferred.defer(task.long_runner_1, req, _queue="my-queue")
example task:
class MyLongRunningTask:
def long_runner_1(self,req):
# do a whole bunch of stuff
channel.send_message(req["cid"], json.dumps({"test":"letting client know task is done"})

Categories