Skip to content

Commit 05f35f0

Browse files
authored
Merge pull request #349 from Secreto31126/emitters-update
Allow emitters to return custom data
2 parents b44cb2d + 0a10cee commit 05f35f0

File tree

9 files changed

+85
-109
lines changed

9 files changed

+85
-109
lines changed

src/emitters.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export type OnSentArgs = {
6464
* @public
6565
* @param args - The arguments object
6666
*/
67-
export type OnMessage = (args: OnMessageArgs) => unknown;
67+
export type OnMessage<Returns> = (args: OnMessageArgs) => Returns;
6868

6969
/**
7070
* @public
@@ -103,6 +103,11 @@ export type OnMessageArgs = {
103103
context?: boolean,
104104
biz_opaque_callback_data?: string
105105
) => Promise<ServerMessageResponse | Response>;
106+
/**
107+
* Utility function for offloading code from the main thread,
108+
* useful for long running tasks such as AI generation
109+
*/
110+
offload: typeof WhatsAppAPI.offload;
106111
/**
107112
* The WhatsAppAPI instance that emitted the event
108113
*/
@@ -115,7 +120,7 @@ export type OnMessageArgs = {
115120
* @public
116121
* @param args - The arguments object
117122
*/
118-
export type OnStatus = (args: OnStatusArgs) => unknown;
123+
export type OnStatus<Returns> = (args: OnStatusArgs) => Returns;
119124

120125
/**
121126
* @public
@@ -157,6 +162,11 @@ export type OnStatusArgs = {
157162
* Arbitrary string included in sent messages
158163
*/
159164
biz_opaque_callback_data?: string;
165+
/**
166+
* Utility function for offloading code from the main thread,
167+
* useful for long running tasks such as AI generation
168+
*/
169+
offload: typeof WhatsAppAPI.offload;
160170
/**
161171
* The raw data from the API
162172
*/

src/index.ts

+30-28
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { DEFAULT_API_VERSION } from "./types.js";
3131
/**
3232
* The main API Class
3333
*/
34-
export class WhatsAppAPI {
34+
export class WhatsAppAPI<EmittersReturnType = void> {
3535
//#region Properties
3636
/**
3737
* The API token
@@ -61,11 +61,6 @@ export class WhatsAppAPI {
6161
* If true, API operations will return the fetch promise instead. Intended for low level debugging.
6262
*/
6363
private parsed: boolean;
64-
/**
65-
* If false, the user functions won't be offloaded from the main event loop.
66-
* Intended for Serverless Environments where the process might be killed after the main function finished.
67-
*/
68-
private offload_functions: boolean;
6964
/**
7065
* If false, the API will be used in a less secure way, removing the need for appSecret. Defaults to true.
7166
*/
@@ -88,9 +83,9 @@ export class WhatsAppAPI {
8883
* ```
8984
*/
9085
public on: {
91-
message?: OnMessage;
86+
message?: OnMessage<EmittersReturnType>;
9287
sent?: OnSent;
93-
status?: OnStatus;
88+
status?: OnStatus<EmittersReturnType>;
9489
} = {};
9590
//#endregion
9691

@@ -125,7 +120,6 @@ export class WhatsAppAPI {
125120
webhookVerifyToken,
126121
v,
127122
parsed = true,
128-
offload_functions = true,
129123
secure = true,
130124
ponyfill = {}
131125
}: WhatsAppAPIConstructorArguments) {
@@ -176,7 +170,6 @@ export class WhatsAppAPI {
176170
}
177171

178172
this.parsed = !!parsed;
179-
this.offload_functions = !!offload_functions;
180173
}
181174

182175
//#region Message Operations
@@ -273,7 +266,7 @@ export class WhatsAppAPI {
273266
response
274267
};
275268

276-
this.user_function(this.on?.sent, args);
269+
WhatsAppAPI.user_function(this.on?.sent, args);
277270

278271
return response ?? promise;
279272
}
@@ -733,7 +726,7 @@ export class WhatsAppAPI {
733726
*
734727
* const token = "token";
735728
* const appSecret = "appSecret";
736-
* const Whatsapp = new WhatsAppAPI(NodeNext({ token, appSecret }));
729+
* const Whatsapp = new WhatsAppAPI<number>(NodeNext({ token, appSecret }));
737730
*
738731
* function handler(req, res) {
739732
* if (req.method == "POST") {
@@ -755,8 +748,10 @@ export class WhatsAppAPI {
755748
* } else res.writeHead(501).end();
756749
* };
757750
*
758-
* Whatsapp.on.message = ({ phoneID, from, message, name }) => {
751+
* Whatsapp.on.message = ({ phoneID, from, message, name, reply, offload }) => {
759752
* console.log(`User ${name} (${from}) sent to bot ${phoneID} a(n) ${message.type}`);
753+
* offload(() => reply(new Text("Hello!")));
754+
* return 202;
760755
* };
761756
*
762757
* const server = createServer(handler);
@@ -766,19 +761,21 @@ export class WhatsAppAPI {
766761
* @param data - The POSTed data object sent by Whatsapp
767762
* @param raw_body - The raw body of the POST request
768763
* @param signature - The x-hub-signature-256 (all lowercase) header signature sent by Whatsapp
769-
* @returns 200, it's the expected http/s response code
764+
* @returns The emitter's return value, undefined if the corresponding emitter isn't set
770765
* @throws 500 if secure and the appSecret isn't specified
771766
* @throws 501 if secure and crypto.subtle or ponyfill isn't available
772767
* @throws 400 if secure and the raw body is missing
773768
* @throws 401 if secure and the signature is missing
774769
* @throws 401 if secure and the signature doesn't match the hash
775770
* @throws 400 if the POSTed data is not a valid Whatsapp API request
771+
* @throws 500 if the user's callback throws an error
772+
* @throws 200, if the POSTed data is valid but not a message or status update (ignored)
776773
*/
777774
async post(
778775
data: PostData,
779776
raw_body?: string,
780777
signature?: string
781-
): Promise<200> {
778+
): Promise<EmittersReturnType | undefined> {
782779
//Validating the payload
783780
if (this.secure) {
784781
if (!this.appSecret) throw 500;
@@ -841,10 +838,15 @@ export class WhatsAppAPI {
841838
context ? message.id : undefined,
842839
biz_opaque_callback_data
843840
),
841+
offload: WhatsAppAPI.offload,
844842
Whatsapp: this
845843
};
846844

847-
this.user_function(this.on?.message, args);
845+
try {
846+
return this.on?.message?.(args);
847+
} catch (error) {
848+
throw 500;
849+
}
848850
} else if ("statuses" in value) {
849851
const statuses = value.statuses[0];
850852

@@ -867,15 +869,20 @@ export class WhatsAppAPI {
867869
pricing,
868870
error,
869871
biz_opaque_callback_data,
872+
offload: WhatsAppAPI.offload,
870873
raw: data
871874
};
872875

873-
this.user_function(this.on?.status, args);
876+
try {
877+
return this.on?.status?.(args);
878+
} catch (error) {
879+
throw 500;
880+
}
874881
}
882+
875883
// If unknown payload, just ignore it
876884
// Facebook doesn't care about your server's opinion
877-
878-
return 200;
885+
throw 200;
879886
}
880887

881888
/**
@@ -993,27 +1000,22 @@ export class WhatsAppAPI {
9931000
* @param f - The user function to call
9941001
* @param a - The arguments to pass to the function
9951002
*/
996-
private user_function<A, F extends ((...a: A[]) => unknown) | undefined>(
997-
f: F,
1003+
private static async user_function<A, F extends (...a: A[]) => unknown>(
1004+
f?: F,
9981005
...a: A[]
9991006
) {
10001007
if (f) {
1001-
if (this.offload_functions) {
1002-
this.offload(f, ...a);
1003-
} else {
1004-
f(...a);
1005-
}
1008+
WhatsAppAPI.offload(f, ...a);
10061009
}
10071010
}
10081011

10091012
/**
10101013
* Offload a function to the next tick of the event loop
10111014
*
1012-
* @internal
10131015
* @param f - The function to offload from the main thread
10141016
* @param a - The arguments to pass to the function
10151017
*/
1016-
private offload<A, F extends (...a: A[]) => unknown>(f: F, ...a: A[]) {
1018+
static offload<A, F extends (...a: A[]) => unknown>(f: F, ...a: A[]) {
10171019
// Thanks @RahulLanjewar93
10181020
Promise.resolve().then(() => f(...a));
10191021
}

src/middleware/adonis.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ export class WhatsAppAPI extends WhatsAppAPIMiddleware {
3333
*/
3434
async handle_post(req: Request) {
3535
try {
36-
return await this.post(
36+
await this.post(
3737
req.body() as PostData,
3838
req.raw() ?? "",
3939
req.header("x-hub-signature-256") ?? ""
4040
);
41+
42+
return 200;
4143
} catch (e) {
4244
// In case who knows what fails ¯\_(ツ)_/¯
4345
return isInteger(e) ? e : 500;

src/middleware/express.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ export class WhatsAppAPI extends WhatsAppAPIMiddleware {
4343
*/
4444
async handle_post(req: Request) {
4545
try {
46-
return this.post(
46+
await this.post(
4747
JSON.parse(req.body ?? "{}"),
4848
req.body,
4949
req.header("x-hub-signature-256")
5050
);
51+
52+
return 200;
5153
} catch (e) {
5254
// In case the JSON.parse fails ¯\_(ツ)_/¯
5355
return isInteger(e) ? e : 500;

src/middleware/globals.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { WhatsAppAPI } from "../index.js";
44
* The abstract class for the middlewares, it extends the WhatsAppAPI class
55
* and defines the handle_post and handle_get methods for its childs.
66
*/
7-
export abstract class WhatsAppAPIMiddleware extends WhatsAppAPI {
7+
export abstract class WhatsAppAPIMiddleware extends WhatsAppAPI<void> {
88
/**
99
* This method should be called when the server receives a POST request.
1010
* Each child implements it differently depending on the framework.

src/middleware/node-http.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ export class WhatsAppAPI extends WhatsAppAPIMiddleware {
6464

6565
if (typeof signature !== "string") throw 400;
6666

67-
return this.post(JSON.parse(body || "{}"), body, signature);
67+
await this.post(JSON.parse(body || "{}"), body, signature);
68+
69+
return 200;
6870
} catch (e) {
6971
// In case the JSON.parse fails ¯\_(ツ)_/¯
7072
return isInteger(e) ? e : 500;

src/middleware/web-standard.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ export class WhatsAppAPI extends WhatsAppAPIMiddleware {
1818
try {
1919
const body = await req.text();
2020

21-
return this.post(
21+
await this.post(
2222
JSON.parse(body || "{}"),
2323
body,
2424
req.headers.get("x-hub-signature-256") ?? ""
2525
);
26+
27+
return 200;
2628
} catch (e) {
2729
// In case the JSON.parse fails ¯\_(ツ)_/¯
2830
return isInteger(e) ? e : 500;

src/types.ts

-5
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,6 @@ export type TheBasicConstructorArguments = {
7070
* Intended for low level debugging.
7171
*/
7272
parsed?: boolean;
73-
/**
74-
* If false, the user functions won't be offloaded from the main event loop.
75-
* Intended for Serverless Environments where the process might be killed after the main function finished.
76-
*/
77-
offload_functions?: boolean;
7873
/**
7974
* If set to false, none of the API checks will be performed, and it will be used in a less secure way.
8075
*

0 commit comments

Comments
 (0)