Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix #248 by adding a new option deferInitialization #1303

Merged
merged 8 commits into from
Feb 14, 2022

Conversation

seratch
Copy link
Member

@seratch seratch commented Feb 4, 2022

Summary

This pull request resolves #248 by adding deferInitialization option along with App#init() method, which enable developers to take full control of the timing to run the initialization steps that require async/await code executions.

JavaScript/TypeScript does not provide any ways to run async functions inside object constructors. That's why the issue #248 has not been resolved for a long time.

This pull request proposes a new option to customize the behavior to overcome the limitation. With the new way, developers can catch the exception that can be thrown by App#init() method:

const { App } = require('@slack/bolt');

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  deferInitialization: true,  // The default value is still false
});

(async () => {
  try {
    await app.init();  // may throw an exception if the given token is invalid
    await app.start(process.env.PORT || 3000);
  } catch (e) {
    console.error(e);
    process.exit(255);
  }
  console.log('⚡️ Bolt app is running!');
})();

Requirements (place an x in each [ ])

@seratch seratch added the enhancement M-T: A feature request for new functionality label Feb 4, 2022
@seratch seratch added this to the 3.10.0 milestone Feb 4, 2022
@seratch seratch self-assigned this Feb 4, 2022
@codecov
Copy link

codecov bot commented Feb 4, 2022

Codecov Report

Merging #1303 (ebb7da2) into main (bbb2553) will decrease coverage by 0.35%.
The diff coverage is 70.83%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1303      +/-   ##
==========================================
- Coverage   73.24%   72.88%   -0.36%     
==========================================
  Files          17       17              
  Lines        1439     1479      +40     
  Branches      431      442      +11     
==========================================
+ Hits         1054     1078      +24     
- Misses        300      311      +11     
- Partials       85       90       +5     
Impacted Files Coverage Δ
src/App.ts 82.71% <70.83%> (-2.35%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bbb2553...ebb7da2. Read the comment docs.

@@ -105,6 +105,7 @@ export interface AppOptions {
socketMode?: boolean;
developerMode?: boolean;
tokenVerificationEnabled?: boolean;
deferInitialization?: boolean;
Copy link
Member Author

Choose a reason for hiding this comment

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

Naming suggestions?

Copy link
Member

Choose a reason for hiding this comment

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

This naming makes the most sense to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is fine

Copy link
Member

@srajiang srajiang left a comment

Choose a reason for hiding this comment

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

This is such a nice addition @seratch!

I tested your branch against a sample app, and noticed that when deferInitialization set as true but still omitting await app.init(); there is no error message for devs.

Do you think we should be enforcing that if deferInitialization is true, the Bolt App should not be starting unless the user has manually called init()?

4-2-2022_defer-init

@@ -105,6 +105,7 @@ export interface AppOptions {
socketMode?: boolean;
developerMode?: boolean;
tokenVerificationEnabled?: boolean;
deferInitialization?: boolean;
Copy link
Member

Choose a reason for hiding this comment

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

This naming makes the most sense to me.

src/App.ts Outdated Show resolved Hide resolved
src/App.ts Outdated Show resolved Hide resolved
installerOptions: this.installerOptions,
});
}
this.receiver = this.initReceiver(
Copy link
Member

Choose a reason for hiding this comment

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

This is so nice! 💯

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, I like splitting up the initialization into discrete functions!

Copy link
Contributor

@filmaj filmaj left a comment

Choose a reason for hiding this comment

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

This is great, thanks for tackling this @seratch !

Not sure if we should bundle the documentation for this feature in this PR, or a separate one, but I think before merging this PR we should at least write a draft of the docs for this support. In my experience, writing up the docs alongside (or even before) the code is written up can help clarify how to structure the code too. I am happy to help write the docs if you would find that helpful - let me know, I can try to create a draft!

@@ -105,6 +105,7 @@ export interface AppOptions {
socketMode?: boolean;
developerMode?: boolean;
tokenVerificationEnabled?: boolean;
deferInitialization?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is fine

installerOptions: this.installerOptions,
});
}
this.receiver = this.initReceiver(
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, I like splitting up the initialization into discrete functions!

src/App.ts Outdated Show resolved Hide resolved
src/App.ts Show resolved Hide resolved
@seratch
Copy link
Member Author

seratch commented Feb 4, 2022

@srajiang Thanks for the great suggestion! After submitting this PR, I was also thinking about the point. Will add a validation in the #start() method.
@filmaj Thanks! I've created a task issue for the document work. If you have the bandwidth, helping me on the doc would be appreciated! #1304

@seratch
Copy link
Member Author

seratch commented Feb 4, 2022

@filmaj @srajiang I've revised all the points in your reviews 👋

// Assert
assert.instanceOf(app, MockApp);
try {
// call #start() before #init()
Copy link
Member Author

Choose a reason for hiding this comment

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

Added this test pattern

"scripts": {
"ngrok": "ngrok http 3000",
"start": "node app.js",
"start": "node http.js",
Copy link
Member Author

Choose a reason for hiding this comment

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

This has been incorrect since I've renamed the source file.


try {
await app.init();
await app.start(process.env.PORT || 3000);
Copy link
Member Author

Choose a reason for hiding this comment

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

If you don't call init() in advance, this start method fails this way:

$ npm start

> bolt-js-custom-properties-app@1.0.0 start /Users/ksera/github/bolt-js/examples/custom-properties
> node http.js

AppInitializationError: This App instance is not yet initialized. Call `await App#init()` before starting the app.
    at App.start (/Users/ksera/github/bolt-js/dist/App.js:239:19)
    at /Users/ksera/github/bolt-js/examples/custom-properties/http.js:46:15
    at Object.<anonymous> (/Users/ksera/github/bolt-js/examples/custom-properties/http.js:52:3)
    at Module._compile (internal/modules/cjs/loader.js:1158:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
    at Module.load (internal/modules/cjs/loader.js:1002:32)
    at Function.Module._load (internal/modules/cjs/loader.js:901:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
    at internal/main/run_main_module.js:18:47 {
  code: 'slack_bolt_app_initialization_error'
}
npm ERR! code ELIFECYCLE
npm ERR! errno 255
npm ERR! bolt-js-custom-properties-app@1.0.0 start: `node http.js`
npm ERR! Exit status 255
npm ERR!

await app.start(process.env.PORT || 3000);

try {
await app.init();
Copy link
Member Author

Choose a reason for hiding this comment

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

Example error pattern:

> bolt-js-custom-properties-app@1.0.0 start /Users/ksera/github/bolt-js/examples/custom-properties
> node http.js

AppInitializationError: Apps used in a single workspace can be initialized with a token. Apps used in many workspaces should be initialized with oauth installer options or authorize.

Since you have not provided a token or authorize, you might be missing one or more required oauth installer options. See https://slack.dev/bolt-js/concepts#authenticating-oauth for these required fields.

    at App.initAuthorizeIfNoTokenIsGiven (/Users/ksera/github/bolt-js/dist/App.js:660:19)
    at App.init (/Users/ksera/github/bolt-js/dist/App.js:180:47)
    at /Users/ksera/github/bolt-js/examples/custom-properties/http.js:46:15
    at Object.<anonymous> (/Users/ksera/github/bolt-js/examples/custom-properties/http.js:53:3)
    at Module._compile (internal/modules/cjs/loader.js:1158:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
    at Module.load (internal/modules/cjs/loader.js:1002:32)
    at Function.Module._load (internal/modules/cjs/loader.js:901:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)
    at internal/main/run_main_module.js:18:47 {
  code: 'slack_bolt_app_initialization_error'
}
npm ERR! code ELIFECYCLE
npm ERR! errno 255
npm ERR! bolt-js-custom-properties-app@1.0.0 start: `node http.js`
npm ERR! Exit status 255

@@ -514,6 +514,11 @@ export default class App {
public start(
...args: Parameters<HTTPReceiver['start'] | SocketModeReceiver['start']>
): ReturnType<HTTPReceiver['start']> {
if (!this.initialized) {
throw new AppInitializationError(
'This App instance is not yet initialized. Call `await App#init()` before starting the app.',
Copy link
Member Author

Choose a reason for hiding this comment

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

Any suggestions on the error message?

Copy link
Member

Choose a reason for hiding this comment

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

This is great!

src/App.spec.ts Outdated Show resolved Hide resolved
Co-authored-by: Sarah Jiang <srajiang@gmail.com>
Copy link
Member

@srajiang srajiang left a comment

Choose a reason for hiding this comment

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

Thanks for turning around these changes! It's looking good to me :)

Copy link
Contributor

@filmaj filmaj left a comment

Choose a reason for hiding this comment

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

Thanks for making the changes! Looks good to me :shipit:

And most definitely I will help with the docs, added to my todo list for this week. I will work on a PR for that.

@seratch
Copy link
Member Author

seratch commented Feb 14, 2022

Thanks for the reviews! Let me merge this PR now.

@seratch seratch merged commit 446622c into slackapi:main Feb 14, 2022
@seratch seratch deleted the issue-248-auth-test-failure branch February 14, 2022 05:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement M-T: A feature request for new functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Initialization includes unhandled async request
3 participants