monday-sdk-js 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,19 +13,6 @@ The monday.com SDK provides a toolset for application developers to build featur
13
13
 
14
14
  The SDK contains methods for server-side and client-side application development. Client-side capabilities assume a valid user session is present (and can seamlessly act on behalf of that user), while server-side methods can be used to access monday.com features using explicit credentials but without any client-side code.
15
15
 
16
- ## Table of contents
17
- - [Usage](#usage)
18
- - [Seamless authentication](#seamless-authentication)
19
- - [SDK capabilities](#sdk-capabilities)
20
- - [`monday.api`](#mondayapiquery-options--)
21
- - [`monday.get`](#mondaygettype-params--)
22
- - [`monday.set`](#mondaysettype-params--)
23
- - [`monday.listen`](#mondaylistentypeortypes-callback-params--)
24
- - [`monday.execute`](#mondayexecutetype-params)
25
- - [`monday.oauth`](#mondayoauthoptions--)
26
- - [`monday.storage`](#mondaystorage)
27
- - [Storage API](#storage-api-mondaystorage)
28
-
29
16
  ## Usage
30
17
 
31
18
  ### Using as an `npm` module
@@ -53,545 +40,6 @@ and then initialize the SDK anywhere in the page by declaring:
53
40
  const monday = window.mondaySdk()
54
41
  ```
55
42
 
56
- ## Seamless authentication
57
- When used for client-side development, SDK methods that require to act on behalf of the connected user will work out-of-the-box by communicating with the parent monday.com running application. You're not required to initialize the SDK client with any explicit credentials.
58
-
59
- Methods that use seamless authentication (including `monday.api` and `monday.storage`) offer capabilities that are scoped based on the permissions of the logged in user and the scopes you have configured in your app.
60
-
61
- ## SDK capabilities
62
-
63
- The SDK exposes the following capabilities:
64
-
65
- | SDK Object | Capability |
66
- |--|--|
67
- | `monday.api` | Performing queries against the monday.com API on behalf of the connected user |
68
- | `monday.listen` | Listen to client-side events on the monday.com client running this app |
69
- | `monday.get` | Retrieve information from the monday.com client running this app |
70
- | `monday.execute` | Call an action on the monday.com client running this app |
71
- | `monday.storage` | Read/write to the Storage API, a key-value storage service for apps |
72
- | `monday.oauth` | Redirecting the client to the OAuth authorization server, with your client ID included |
73
-
74
- <br/>
75
-
76
- ### **`monday.api(query, options = {})`**
77
- Used for querying the monday.com GraphQL API seamlessly on behalf of the connected user, or using a provided API token.
78
-
79
- **Parameters:**
80
-
81
- - `query`: A [GraphQL](https://graphql.org/) query, can be either a *query* (retrieval operation) or a *mutation* (creation/update/deletion operation). Placeholders may be used, which will be substituted by the `variables` object passed within the options.
82
- - `options`:
83
-
84
- | Option | Description| Required | Default |
85
- | --------- | ----------------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------- |
86
- | `token` | Access token for the API | Only on server | If not set, will use the credentials of the current user (client only) |
87
- | `variables` | An object containing GraphQL query variables | No | |
88
-
89
- Instead of passing the API token to the `api()` method on each request, you can set the API token once using:
90
- ```js
91
- monday.setToken('mytoken')
92
- ```
93
-
94
- **Returns:**
95
-
96
- A `Promise` that will be `resolved` to the API response.
97
- <br>If there was an unhandled GraphQL error in the API, a `Promise` will be `rejected` with an Error.
98
- In case of handled errors from GraphQL API (response with the 200 status), a `Promise` will be `resolved` with the API response.
99
- <br>You can check the list of GraphQL API errors [here](https://monday.com/developers/v2#errors-section).
100
-
101
-
102
- **Examples:**
103
-
104
- A **client-side** query that fetch the ID and name of all the users within the account that the connected user is allowed to view:
105
- ```javascript
106
- monday.api(`query { users { id, name } }`).then(res => {
107
- console.log(res);
108
- /* { data: { users: [{id: 12312, name: "Bart Simpson"}, {id: 423423, name: "Homer Simpson"}] } } */
109
- });
110
- ```
111
- A **server-side** query that fetches all the names of users in the account:
112
- ```js
113
- monday.setToken('ac5eb492f8c...');
114
- monday.api('query { users { name } }').then(res => {...})
115
- ```
116
-
117
- A mutation that sends an in-app notification to user `user_id`, which upon clicking will take the user to item `item_id`:
118
- ```javascript
119
- monday.api(`
120
- mutation {
121
- create_notification(
122
- text: "I've got a notification for you!",
123
- user_id: ${user_id},
124
- target_id: ${item_id},
125
- target_type: Project,
126
- internal: true
127
- ) {
128
- id
129
- }
130
- }
131
- `);
132
- ```
133
-
134
- For more information about the GraphQL API and all queries and mutations possible, read the [API Documentation](https://monday.com/developers/v2)
135
-
136
- <br/>
137
-
138
- ### **`monday.get(type, params = {})`**
139
-
140
- Used for retrieving data from the parent monday.com application where your app is currently running. This object can only be used when your app is running inside an `iframe`. This can only be used in client-side apps.
141
-
142
-
143
- **Parameters:**
144
-
145
- - `type`: The type of requested information (available values below)
146
- - `params`: Reserved for future use
147
-
148
- The available types that can be requested are:
149
- | Type | Description |
150
- |--|--|
151
- | `'context'` | Information about where this app is currently displayed, depending on the type of feature |
152
- | `'settings'` | The application settings as configured by the user that installed the app |
153
- | `'itemIds'` | The list of item IDs that are filtered in the current board (or all items if no filters are applied) |
154
- | `'sessionToken'` | A JWT token which is decoded with your app's secret and can be used as a session token between your app's frontend & backend |
155
- | `'filter'` | The state of the _Search_ filter |
156
-
157
- **Returns:**
158
-
159
- A `Promise` that will be resolved with the requested data.
160
-
161
- **Examples:**
162
-
163
- Requesting context and settings data:
164
- ```js
165
- monday.get("settings").then(res => ...);
166
- monday.get("context").then(res => ...);
167
- ```
168
-
169
- Example context objects that return for a board view and a dashboard widget:
170
- ```js
171
- // Board view context
172
- {
173
- "boardViewId": 19324,
174
- "boardId": 3423243,
175
- "mode": "fullScreen", // or "split"
176
- "theme": "light" // or "dark"
177
- }
178
-
179
- // Dashboard widget context
180
- {
181
- "widgetId": 54236,
182
- "boardIds": [3423243, 943728],
183
- "theme": "light" // or "dark"
184
- }
185
- ```
186
-
187
- Requesting the list of items currently in view in the board:
188
-
189
- ```js
190
- monday.get("itemIds").then(res => console.log(res));
191
- // => [234234, 4564, 234234, 67675, 576567]
192
- ```
193
-
194
- <br/>
195
-
196
- ### **`monday.set(type, params = {})`**
197
-
198
- Used for setting up data inside your application. This method can only be used when your app is running inside an `iframe`. This can only be used in client-side apps.
199
-
200
-
201
- **Parameters:**
202
-
203
- - `type`: The type of data that can be set (available values below)
204
- - `params`: Optional parameters for the action
205
-
206
- The available types that can be set are:
207
- | Type | Description |
208
- |--|--|
209
- | `'settings'` | The application settings as configured by the user that installed the app |
210
-
211
- **Returns:**
212
-
213
- A `Promise` that will be `resolved` to the set method response.
214
-
215
- **Examples:**
216
-
217
- Setting application settings data:
218
- ```js
219
- monday.set("settings").then(res => ...);
220
- ```
221
-
222
- Example application settings object that we are able to set for a board view:
223
- ```js
224
- // Board view settings example
225
- {
226
- "text": "textual value",
227
- "color": "#037f4c",
228
- "date": "2022-08-25"
229
- "checkbox1": false
230
- "textarea": "line1\nline2\n.....\nline n"
231
- }
232
- ```
233
- <br/>
234
-
235
- ### **`monday.listen(typeOrTypes, callback, params = {})`**
236
-
237
- Creates a listener which allows subscribing to certain types of client-side events.
238
-
239
- **Parameters:**
240
-
241
- - `typeOrTypes`: The type, or array of types, of events to subscribe to
242
- - `callback`: A callback function that is fired when the listener is triggered by a client-side event
243
- - `params`: Reserved for future use
244
-
245
- You can subscribe to the following types of events:
246
- | Type | Description |
247
- |--|--|
248
- | `'context'` | Fired when one of the parameters in the context changes |
249
- | `'settings'` | Fired when a setting value is changed by the user |
250
- | `'itemIds'` | Fired when the board filter changes, which impacts the list of items currently in view |
251
- | `'events'` | Fired when an interaction takes place with the board/dashboard |
252
- | `'filter'` | Fired when the _Search_ filter changes |
253
-
254
- **Returns:**
255
-
256
- This method does not have a return value.
257
-
258
- **Examples:**
259
-
260
- Subscribe to changes in settings and context:
261
- ```js
262
- const callback = res => console.log(res);
263
- monday.listen(['settings', 'context'], callback);
264
- ```
265
-
266
- Subscribe to interaction-based events on the board:
267
- ```js
268
- const callback = res => console.log(res);
269
- const unsubscribe = monday.listen("events", callback);
270
-
271
- // When an item/s are created on the board:
272
- // => { type: "new_items", itemIds: [5543, 5544, 5545], boardId: 3425 }
273
-
274
- // When a column value changes for one of the items:
275
- // => { type: "change_column_value", itemId: 12342, value: {...} }
276
- ```
277
- <br/>
278
-
279
- ### **`monday.execute(type, params)`**
280
- Invokes an action on the parent monday client.
281
-
282
- **Parameters:**
283
-
284
- - `type`: Which action to perform
285
- - `params`: Optional parameters for the action
286
-
287
- **Returns:**
288
-
289
- A `Promise` that will optionally be resolved to the return value from the action executed
290
-
291
-
292
- **Action types:**
293
-
294
- #### Open item card
295
- Opens a modal with information from the selected item
296
-
297
- **type**
298
- `'openItemCard'`
299
-
300
- **params**
301
-
302
- | Parameter|Type | Description | Required | Default Value |
303
- | --- | --- | --- | --- | --- |
304
- | itemId| Integer | The ID of the item to open | Yes | |
305
- |kind | String | On which view to open the item card. <br>Can be "updates" / "columns" | No |"columns" |
306
-
307
- **Example**
308
- ```javascript
309
- monday.execute('openItemCard', { itemId: item.id });
310
- ```
43
+ ## Docs
311
44
 
312
- #### Confirmation dialog
313
- Opens a confirmation dialog to the user
314
- **type**
315
- `'confirm'`
316
-
317
- **params**
318
-
319
- | Parameter|Type | Description | Required | Default Value |
320
- | --- |---|--- | --- | --- |
321
- | message|String | The message to display in the dialog| Yes | |
322
- |confirmButton|String| The text for the confirmation button | No |"OK" |
323
- |cancelButton|String| The text for the cancel button | No |"Cancel" |
324
- |excludeCancelButton|Boolean| Either to exclude the cancel button | No |false|
325
-
326
- **Example**
327
- ```js
328
- monday.execute("confirm", {
329
- message: "Are you sure?",
330
- confirmButton: "Let's go!",
331
- cancelButton: "No way",
332
- excludeCancelButton: false
333
- }).then((res) => {
334
- console.log(res.data);
335
- // {"confirm": true}
336
- });
337
- ```
338
-
339
- #### Notice message
340
- Display a message at the top of the user's page. Usefull for success, error & general messages.
341
-
342
- **type**
343
- `'notice'`
344
-
345
- **params**
346
-
347
- | Parameter|Type | Description | Required | Default Value |
348
- | --- |---|--- | --- | --- |
349
- | message|String | The message to display| Yes | |
350
- |type|String| The type of message to display . Can be "success" (green), "error" (red) or "info" (blue) | No |"info" |
351
- |timeout|Integer| The number of milliseconds to show the message until it closes | No | 5000 |
352
-
353
- **Example**
354
- ```js
355
- monday.execute("notice", {
356
- message: "I'm a success message",
357
- type: "success", // or "error" (red), or "info" (blue)
358
- timeout: 10000,
359
- });
360
- ```
361
-
362
- #### Open files preview dialog
363
- Opens a modal with the preview of an asset
364
-
365
- **type**
366
- `'openFilesDialog'`
367
-
368
- **params**
369
-
370
- | Parameter|Type | Description | Required | Default Value |
371
- | --- | --- | --- | --- | --- |
372
- | boardId| Integer | The ID of the board | Yes | |
373
- | itemId| Integer | The ID of the item, which contains an asset | Yes | |
374
- | columnId| String | The ID of the column, which contains an asset | Yes | |
375
- | assetId| Integer | The ID of the asset to open | Yes | |
376
-
377
- **Example**
378
- ```javascript
379
- monday.execute('openFilesDialog', {
380
- boardId: 12345,
381
- itemId: 23456,
382
- columnId: 'files',
383
- assetId: 34567
384
- })
385
- ```
386
-
387
- #### Trigger file upload process
388
- Opens a modal to let the current user upload a file to a specific file column.
389
-
390
- Returns a promise. In case of error, the promise is rejected
391
-
392
- After the file is successfully uploaded, the "change_column_value" event will be triggered.
393
- See the [`monday.listen`](#mondaylistentypeortypes-callback-params--)('events', callback) method to subscribe to these events.
394
-
395
- *Requires boards:write scope*
396
-
397
- **type**
398
- `'triggerFilesUpload'`
399
-
400
- **params**
401
-
402
- | Parameter|Type | Description | Required | Default Value |
403
- | --- | --- | --- | --- | --- |
404
- | boardId| Integer | The ID of the board | Yes | |
405
- | itemId| Integer | The ID of the item, which contains an asset | Yes | |
406
- | columnId| String | The ID of the file column, where file should be uploaded | Yes | |
407
-
408
- **Example**
409
- ```javascript
410
- monday.execute('triggerFilesUpload', {
411
- boardId: 12345,
412
- itemId: 23456,
413
- columnId: 'files'
414
- })
415
- ```
416
-
417
- #### Open modal
418
- Opens a new modal window as an iFrame.
419
-
420
- **type**
421
- `'openAppFeatureModal'`
422
-
423
- **params**
424
-
425
- | Parameter|Type | Description | Required | Default Value |
426
- | --- | --- | --- | --- | --- |
427
- | url | String | The URL of the page displayed in the modal | No | current iFrame's URL |
428
- | urlPath | String | Subdirectory or path of the URL to open | No | |
429
- | urlParams | Object | Query parameters for the URL | No | |
430
- | width | String | The width of the modal | No | "0px" |
431
- | height | String | The height of the modal | No | "0px" |
432
-
433
- **Example**
434
- ```javascript
435
- monday.execute('openAppFeatureModal', { urlPath, urlParams, height, width }).then((res) => {
436
- console.log(res.data);
437
- {"close": true}
438
- // The above is a callback to see if a user closed the modal from the inside. This is useful should you want to run some logic within the app window.
439
- });
440
- ```
441
-
442
- Note: make sure the urlPath you pass is a relative URL and not an absolute URL.
443
-
444
- #### Close modal
445
- Closes the modal window.
446
-
447
- **type**
448
- `'closeAppFeatureModal'`
449
-
450
- **params**
451
- This method does not have any parameters.
452
-
453
- **Example**
454
- ```javascript
455
- monday.execute('closeAppFeatureModal').then((res) => {
456
- console.log(res.data);
457
- });
458
- ```
459
-
460
- #### Value created for user
461
- Notifies the monday platform that user gains a first value in an app.
462
-
463
- **type**
464
- `'value'`
465
-
466
- **params**
467
- This method does not have any parameters.
468
-
469
- **Example**
470
- ```javascript
471
- monday.execute('valueCreatedForUser').then((res) => {
472
- console.log(res.data);
473
- });
474
- ```
475
-
476
- #### Open setting window
477
- Opens view settings window
478
-
479
- **type**
480
- `'value'`
481
-
482
- **params**
483
- This method does not have any parameters.
484
-
485
- **Example**
486
- ```javascript
487
- monday.execute('openSettings').then((res) => {
488
- console.log(res.data);
489
- // note that method will open view settings, unless settings were alreday opened
490
- });
491
- ```
492
-
493
- #### Close setting window
494
- Closes view settings window
495
-
496
- **type**
497
- `'value'`
498
-
499
- **params**
500
- This method does not have any parameters.
501
-
502
- **Example**
503
- ```javascript
504
- monday.execute('closeSettings').then((res) => {
505
- console.log(res.data);
506
- // note that method will close view settings, unless settings were alreday closed
507
- });
508
- ```
509
-
510
- ### **`monday.oauth(options = {})`**
511
- Performs a client-side redirection of the user to the monday OAuth screen with your client ID embedded in the URL, in order to get their approval to generate a temporary OAuth token based on your requested permission scopes.
512
-
513
- **Parameters:**
514
-
515
- - `options`: An object with options as specified below
516
-
517
- | Option | Required |Description |
518
- |--|--|--|
519
- | `clientId` | No, defaults to your client ID | The OAuth client ID of the requesting application |
520
- | `mondayOauthUrl`| No | The URL of the monday OAuth endpoint |
521
-
522
- **Returns:**
523
-
524
- This method does not have a return value.
525
-
526
- <br/>
527
-
528
- ### **`monday.storage`**
529
- Provides access to the Storage API. See below for methods and explanation.
530
-
531
- <br/>
532
-
533
-
534
- ## Storage API (`monday.storage`)
535
- > The Storage API is in early beta stages, its API is likely to change
536
-
537
- The monday apps infrastructure includes a persistent, key-value database storage that developers can leverage to store data without having to create their own backend and maintain their own database.
538
-
539
- The database currently offers instance-level storage only, meaning that each application instance (i.e. a single board view or a dashboard widget) maintains its own storage. Apps cannot share storage across accounts or even across apps installed in the same location.
540
-
541
- **Available methods:**
542
-
543
- - `monday.storage.instance.getItem(key)` - Returns a stored value from the database under `key`
544
- - `monday.storage.instance.setItem(key, value)` - Stores `value` under `key` in the database
545
- <!-- - `monday.storage.instance.deleteItem(key)` - Deletes the value under `key` -->
546
-
547
-
548
- **Returns:**
549
-
550
- All methods return a `Promise` which will be resolved to the Storage API's response
551
-
552
- **Versioning:**
553
-
554
- You may face cases where multiple monday.com users will be working on the same app instance and writing to the same key in an unsynchronized fashion. If you're storing a compound data structure (like JSON) in that key, such operations may overwrite each other.
555
-
556
- The `getItem()` and `setItem()` each return a *version identifier* which can be used to identify which value is currently stored in a key. Whenever a write that changes the value occurs, the version identifier in the database changes. This allows you to identify whether a value was already changed from another location and prevent that from being overwritten.
557
-
558
- Example of using versioning:
559
- ```js
560
- monday.storage.instance.getItem('serialKey').then(res => {
561
- const { value, version } = res.data;
562
- sleep(10000); // someone may overwrite serialKey during this time
563
-
564
- monday.storage.instance.setItem('serialKey', { previous_version: version }).then(res => {
565
- console.log(res);
566
- }
567
- });
568
- // => '{ "success": false, "reason": "version_conflict" }'
569
- ```
570
-
571
- **Examples:**
572
-
573
- Store a value in the database:
574
- ```js
575
- monday.storage.instance.setItem('mykey', 'Lorem Ipsum').then(res => {
576
- console.log(res);
577
- });
578
- // => { "success": true }
579
- ```
580
-
581
- Retrieve a previously stored value in the database:
582
- ```js
583
- monday.storage.instance.getItem('mykey').then(res => {
584
- console.log(res.data.value);
585
- });
586
- // => 'Lorem Ipsum'
587
- ```
588
-
589
- <!--
590
- Delete a previously stored key in the database:
591
- ```js
592
- monday.storage.instance.deleteItem('mykey').then(res => {
593
- console.log(res);
594
- }
595
- // => { "success": true }
596
- ```
597
- -->
45
+ To get started, check out the [SDK Documentation](https://developer.monday.com/apps/docs/introduction-to-the-sdk)
package/dist/main.js CHANGED
@@ -1 +1 @@
1
- !function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=3)}([function(e,t){const n="undefined"!=typeof window&&void 0!==window.document;e.exports={convertToArrayIfNeeded:e=>Array.isArray(e)?e:[e],isBrowser:n}},function(e,t,n){e.exports=n(5)},function(e,t,n){(function(t){const{isBrowser:r}=n(0),i=!r&&!1,o=i&&t.env.MONDAY_COM_PROTOCOL||"https",s=i&&t.env.MONDAY_COM_DOMAIN||"monday.com",a=`${o}://api.${s}/v2`,c=`${o}://auth.${s}/oauth2/authorize`,u=`${o}://auth.${s}/oauth2/token`;e.exports={MONDAY_DOMAIN:s,MONDAY_PROTOCOL:o,MONDAY_API_URL:a,MONDAY_OAUTH_URL:c,MONDAY_OAUTH_TOKEN_URL:u}}).call(this,n(6))},function(e,t,n){var r,i;const{isBrowser:o}=n(0),s=n(o?4:12);"undefined"!=typeof self&&self,void 0===(i="function"==typeof(r=function(){return window.mondaySdk=s,s})?r.call(t,n,t,e):r)||(e.exports=i)},function(e,t,n){const r=n(1),{MONDAY_OAUTH_URL:i}=n(2),{convertToArrayIfNeeded:o}=n(0),{initScrollHelperIfNeeded:s}=n(9),{initBackgroundTracking:a}=n(10),c=[];class u{constructor(e={}){this._clientId=e.clientId,this._apiToken=e.apiToken,this.listeners={},this.setClientId=this.setClientId.bind(this),this.setToken=this.setToken.bind(this),this.api=this.api.bind(this),this.listen=this.listen.bind(this),this.get=this.get.bind(this),this.set=this.set.bind(this),this.execute=this.execute.bind(this),this.oauth=this.oauth.bind(this),this._receiveMessage=this._receiveMessage.bind(this),this.storage={instance:{setItem:this.setStorageInstanceItem.bind(this),getItem:this.getStorageInstanceItem.bind(this),deleteItem:this.deleteStorageInstanceItem.bind(this)}},window.addEventListener("message",this._receiveMessage,!1),e.withoutScrollHelper||s(),a(this)}setClientId(e){this._clientId=e}setToken(e){this._apiToken=e}api(e,t={}){const n={query:e,variables:t.variables},i=t.token||this._apiToken;return i?r.execute(n,i):new Promise((e,t)=>{this._localApi("api",{params:n}).then(t=>{e(t.data)}).catch(e=>t(e))})}listen(e,t,n){o(e).forEach(e=>{this._addListener(e,t),this._localApi("listen",{type:e,params:n})})}get(e,t){return this._localApi("get",{type:e,params:t})}set(e,t){return this._localApi("set",{type:e,params:t})}execute(e,t){return this._localApi("execute",{type:e,params:t})}track(e,t){return this.execute("track",{name:e,data:t})}oauth(e={}){const t=e.clientId||this._clientId;if(!t)throw new Error("clientId is required");const n=`${e.mondayOauthUrl||i}?client_id=${t}`;window.location=n}setStorageInstanceItem(e,t,n={}){return this._localApi("storage",{method:"set",key:e,value:t,options:n,segment:"instance"})}getStorageInstanceItem(e,t={}){return this._localApi("storage",{method:"get",key:e,options:t,segment:"instance"})}deleteStorageInstanceItem(e,t={}){return this._localApi("storage",{method:"delete",key:e,options:t,segment:"instance"})}_localApi(e,t){return new Promise((r,i)=>{const o=this._generateRequestId(),s=this._clientId,a=n(11).version;window.parent.postMessage({method:e,args:t,requestId:o,clientId:s,version:a},"*"),this._addListener(o,e=>{if(e.errorMessage){const t=new Error(e.errorMessage);t.data=e.data,i(t)}else r(e)})})}_receiveMessage(e){const{method:t,type:n,requestId:r}=e.data;let i=[...this.listeners[t]||c,...this.listeners[n]||c,...this.listeners[r]||c];i&&i.forEach(t=>{try{t(e.data)}catch(e){console.error("Message callback error: ",e)}})}_addListener(e,t){this.listeners[e]=this.listeners[e]||[],this.listeners[e].push(t)}_generateRequestId(){return Math.random().toString(36).substr(2,9)}_removeEventListener(){window.removeEventListener("message",this._receiveMessage,!1)}_clearListeners(){this.listeners=[]}}e.exports=function(e={}){return new u(e)}},function(e,t,n){const{MONDAY_API_URL:r,MONDAY_OAUTH_TOKEN_URL:i}=n(2),o=n(7),s="Could not parse JSON from monday.com's GraphQL API response",a="Token is required",c="Received timeout from monday.com's GraphQL API";e.exports={execute:async function(e,t,n={}){if(!t&&n.url!==i)throw new Error(a);const u=`${n.url||r}${n.path||""}`;let l=await function(e,t,n,r={}){return o.nodeFetch(e,{method:r.method||"POST",body:JSON.stringify(t||{}),headers:{Authorization:n,"Content-Type":"application/json"}})}(u,e,t,n);const d=l.status,h=l.headers.get("content-type");if(!h||!h.includes("application/json")){if(504===d)throw new Error(c);const e=await l.text();throw new Error(e)}try{return await l.json()}catch(e){throw new Error(s)}},COULD_NOT_PARSE_JSON_RESPONSE_ERROR:s,TOKEN_IS_REQUIRED_ERROR:a,API_TIMEOUT_ERROR:c}},function(e,t){var n,r,i=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function a(e){if(n===setTimeout)return setTimeout(e,0);if((n===o||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:o}catch(e){n=o}try{r="function"==typeof clearTimeout?clearTimeout:s}catch(e){r=s}}();var c,u=[],l=!1,d=-1;function h(){l&&c&&(l=!1,c.length?u=c.concat(u):d=-1,u.length&&p())}function p(){if(!l){var e=a(h);l=!0;for(var t=u.length;t;){for(c=u,u=[];++d<t;)c&&c[d].run();d=-1,t=u.length}c=null,l=!1,function(e){if(r===clearTimeout)return clearTimeout(e);if((r===s||!r)&&clearTimeout)return r=clearTimeout,clearTimeout(e);try{r(e)}catch(t){try{return r.call(null,e)}catch(t){return r.call(this,e)}}}(e)}}function f(e,t){this.fun=e,this.array=t}function m(){}i.nextTick=function(e){var t=new Array(arguments.length-1);if(arguments.length>1)for(var n=1;n<arguments.length;n++)t[n-1]=arguments[n];u.push(new f(e,t)),1!==u.length||l||a(p)},f.prototype.run=function(){this.fun.apply(null,this.array)},i.title="browser",i.browser=!0,i.env={},i.argv=[],i.version="",i.versions={},i.on=m,i.addListener=m,i.once=m,i.off=m,i.removeListener=m,i.removeAllListeners=m,i.emit=m,i.prependListener=m,i.prependOnceListener=m,i.listeners=function(e){return[]},i.binding=function(e){throw new Error("process.binding is not supported")},i.cwd=function(){return"/"},i.chdir=function(e){throw new Error("process.chdir is not supported")},i.umask=function(){return 0}},function(e,t,n){const r=n(8);e.exports={nodeFetch:function(e,t={}){return r(e,t)}}},function(e,t,n){"use strict";var r=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==r)return r;throw new Error("unable to locate global object")}();e.exports=t=r.fetch,t.default=r.fetch.bind(r),t.Headers=r.Headers,t.Request=r.Request,t.Response=r.Response},function(e,t){let n=!1;e.exports={initScrollHelperIfNeeded:function(){if(n)return;n=!0;const e=document.createElement("style");e.appendChild(document.createTextNode('body::before { content: ""; position: fixed; top: 0; right: 0; bottom: 0; left: 0; pointer-events: none; z-index: 2147483647; /* mondaySdk css - can be disabled with: mondaySdk({withoutScrollHelper: true }) */ }')),(document.head||document.getElementsByTagName("head")[0]).appendChild(e)}}},function(e,t){let n=!1;e.exports={initBackgroundTracking:e=>{if(n)return;n=!0;const t=()=>{e.track("ping")};t(),setInterval(t,3e5)}}},function(e){e.exports=JSON.parse('{"name":"monday-sdk-js","version":"0.1.3","private":false,"repository":"https://github.com/mondaycom/monday-sdk-js","main":"src/index.js","author":"talharamati <tal@monday.com>","license":"MIT","files":["LICENSE","README.md","dist/","src/","server-sdk.js"],"dependencies":{"@types/source-map":"^0.5.2","node-fetch":"^2.6.0"},"devDependencies":{"@babel/cli":"^7.6.0","@babel/core":"^7.6.0","@babel/node":"^7.6.1","@babel/preset-env":"^7.6.0","@babel/preset-react":"^7.0.0","@babel/register":"^7.6.0","babel-loader":"^8.0.6","chai":"^4.2.0","eslint":"^6.8.0","jsdom":"^16.2.0","mocha":"^7.1.0","prettier":"^1.19.1","sinon":"^9.0.0","sinon-chai":"^3.5.0","webpack":"^4.38.0","webpack-cli":"^3.3.6","webpack-dev-server":"^3.7.2"},"scripts":{"start":"webpack-dev-server","build":"webpack --mode=production --env.WEBPACK_BUILD=true","test":"mocha \'./src/**/*-test.js\'","test:watch":"mocha \'./src/**/*-test.js\' --watch","precommit":"yarn lint && yarn style-check","lint":"eslint \'./src/**/*.*\'","style-check":"prettier --check \'./src/**/*.js\'"}}')},function(e,t,n){const r=n(1),{oauthToken:i}=n(13),o="Should send 'token' as an option or call mondaySdk.setToken(TOKEN)";class s{constructor(e={}){this._token=e.token,this.setToken=this.setToken.bind(this),this.api=this.api.bind(this)}setToken(e){this._token=e}async api(e,t={}){const n={query:e,variables:t.variables},i=t.token||this._token;if(!i)throw new Error(o);return await r.execute(n,i)}oauthToken(e,t,n){return i(e,t,n)}}e.exports=function(e={}){return new s(e)}},function(e,t,n){const{execute:r}=n(1),{MONDAY_OAUTH_TOKEN_URL:i}=n(2);e.exports={oauthToken:(e,t,n)=>{return r({code:e,client_id:t,client_secret:n},null,{url:i})}}}]);
1
+ !function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=3)}([function(e,t){const n="undefined"!=typeof window&&void 0!==window.document;e.exports={convertToArrayIfNeeded:e=>Array.isArray(e)?e:[e],isBrowser:n}},function(e,t,n){e.exports=n(5)},function(e,t,n){(function(t){const{isBrowser:r}=n(0),i=!r&&!1,o=i&&t.env.MONDAY_COM_PROTOCOL||"https",s=i&&t.env.MONDAY_COM_DOMAIN||"monday.com",a=`${o}://api.${s}/v2`,c=`${o}://auth.${s}/oauth2/authorize`,u=`${o}://auth.${s}/oauth2/token`;e.exports={MONDAY_DOMAIN:s,MONDAY_PROTOCOL:o,MONDAY_API_URL:a,MONDAY_OAUTH_URL:c,MONDAY_OAUTH_TOKEN_URL:u}}).call(this,n(6))},function(e,t,n){var r,i;const{isBrowser:o}=n(0),s=n(o?4:12);"undefined"!=typeof self&&self,void 0===(i="function"==typeof(r=function(){return window.mondaySdk=s,s})?r.call(t,n,t,e):r)||(e.exports=i)},function(e,t,n){const r=n(1),{MONDAY_OAUTH_URL:i}=n(2),{convertToArrayIfNeeded:o}=n(0),{initScrollHelperIfNeeded:s}=n(9),{initBackgroundTracking:a}=n(10),c=[];class u{constructor(e={}){this._clientId=e.clientId,this._apiToken=e.apiToken,this.listeners={},this.setClientId=this.setClientId.bind(this),this.setToken=this.setToken.bind(this),this.api=this.api.bind(this),this.listen=this.listen.bind(this),this.get=this.get.bind(this),this.set=this.set.bind(this),this.execute=this.execute.bind(this),this.oauth=this.oauth.bind(this),this._receiveMessage=this._receiveMessage.bind(this),this.storage={instance:{setItem:this.setStorageInstanceItem.bind(this),getItem:this.getStorageInstanceItem.bind(this),deleteItem:this.deleteStorageInstanceItem.bind(this)}},window.addEventListener("message",this._receiveMessage,!1),e.withoutScrollHelper||s(),a(this)}setClientId(e){this._clientId=e}setToken(e){this._apiToken=e}api(e,t={}){const n={query:e,variables:t.variables},i=t.token||this._apiToken;return i?r.execute(n,i):new Promise((e,t)=>{this._localApi("api",{params:n}).then(t=>{e(t.data)}).catch(e=>t(e))})}listen(e,t,n){const r=o(e),i=[];return r.forEach(e=>{i.push(this._addListener(e,t)),this._localApi("listen",{type:e,params:n})}),()=>{i.forEach(e=>e())}}get(e,t){return this._localApi("get",{type:e,params:t})}set(e,t){return this._localApi("set",{type:e,params:t})}execute(e,t){return this._localApi("execute",{type:e,params:t})}track(e,t){return this.execute("track",{name:e,data:t})}oauth(e={}){const t=e.clientId||this._clientId;if(!t)throw new Error("clientId is required");const n=`${e.mondayOauthUrl||i}?client_id=${t}`;window.location=n}setStorageInstanceItem(e,t,n={}){return this._localApi("storage",{method:"set",key:e,value:t,options:n,segment:"instance"})}getStorageInstanceItem(e,t={}){return this._localApi("storage",{method:"get",key:e,options:t,segment:"instance"})}deleteStorageInstanceItem(e,t={}){return this._localApi("storage",{method:"delete",key:e,options:t,segment:"instance"})}_localApi(e,t){return new Promise((r,i)=>{const o=this._generateRequestId(),s=this._clientId,a=n(11).version;window.parent.postMessage({method:e,args:t,requestId:o,clientId:s,version:a},"*");const c=this._addListener(o,e=>{if(c(),e.errorMessage){const t=new Error(e.errorMessage);t.data=e.data,i(t)}else r(e)})})}_receiveMessage(e){const{method:t,type:n,requestId:r}=e.data,i=this.listeners[t]||c,o=this.listeners[n]||c,s=this.listeners[r]||c;let a=new Set([...i,...o,...s]);a&&a.forEach(t=>{try{t(e.data)}catch(e){console.error("Message callback error: ",e)}})}_addListener(e,t){return this.listeners[e]=this.listeners[e]||new Set,this.listeners[e].add(t),()=>{this.listeners[e].delete(t),0===this.listeners[e].size&&delete this.listeners[e]}}_generateRequestId(){return Math.random().toString(36).substring(2,9)}_removeEventListener(){window.removeEventListener("message",this._receiveMessage,!1)}_clearListeners(){this.listeners=[]}}e.exports=function(e={}){return new u(e)}},function(e,t,n){const{MONDAY_API_URL:r,MONDAY_OAUTH_TOKEN_URL:i}=n(2),o=n(7);e.exports={execute:async function(e,t,n={}){if(!t&&n.url!==i)throw new Error("Token is required");const s=`${n.url||r}${n.path||""}`;let a=await function(e,t,n,r={}){return o.nodeFetch(e,{method:r.method||"POST",body:JSON.stringify(t||{}),headers:{Authorization:n,"Content-Type":"application/json"}})}(s,e,t,n);const c=a.status,u=a.headers.get("content-type");if(!u||!u.includes("application/json")){if(504===c)throw new Error("Received timeout from monday.com's GraphQL API");const e=await a.text();throw new Error(e)}try{return await a.json()}catch(e){throw new Error("Could not parse JSON from monday.com's GraphQL API response")}},COULD_NOT_PARSE_JSON_RESPONSE_ERROR:"Could not parse JSON from monday.com's GraphQL API response",TOKEN_IS_REQUIRED_ERROR:"Token is required",API_TIMEOUT_ERROR:"Received timeout from monday.com's GraphQL API"}},function(e,t){var n,r,i=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function a(e){if(n===setTimeout)return setTimeout(e,0);if((n===o||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:o}catch(e){n=o}try{r="function"==typeof clearTimeout?clearTimeout:s}catch(e){r=s}}();var c,u=[],l=!1,d=-1;function h(){l&&c&&(l=!1,c.length?u=c.concat(u):d=-1,u.length&&p())}function p(){if(!l){var e=a(h);l=!0;for(var t=u.length;t;){for(c=u,u=[];++d<t;)c&&c[d].run();d=-1,t=u.length}c=null,l=!1,function(e){if(r===clearTimeout)return clearTimeout(e);if((r===s||!r)&&clearTimeout)return r=clearTimeout,clearTimeout(e);try{r(e)}catch(t){try{return r.call(null,e)}catch(t){return r.call(this,e)}}}(e)}}function f(e,t){this.fun=e,this.array=t}function m(){}i.nextTick=function(e){var t=new Array(arguments.length-1);if(arguments.length>1)for(var n=1;n<arguments.length;n++)t[n-1]=arguments[n];u.push(new f(e,t)),1!==u.length||l||a(p)},f.prototype.run=function(){this.fun.apply(null,this.array)},i.title="browser",i.browser=!0,i.env={},i.argv=[],i.version="",i.versions={},i.on=m,i.addListener=m,i.once=m,i.off=m,i.removeListener=m,i.removeAllListeners=m,i.emit=m,i.prependListener=m,i.prependOnceListener=m,i.listeners=function(e){return[]},i.binding=function(e){throw new Error("process.binding is not supported")},i.cwd=function(){return"/"},i.chdir=function(e){throw new Error("process.chdir is not supported")},i.umask=function(){return 0}},function(e,t,n){const r=n(8);e.exports={nodeFetch:function(e,t={}){return r(e,t)}}},function(e,t,n){"use strict";var r=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==r)return r;throw new Error("unable to locate global object")}();e.exports=t=r.fetch,t.default=r.fetch.bind(r),t.Headers=r.Headers,t.Request=r.Request,t.Response=r.Response},function(e,t){let n=!1;e.exports={initScrollHelperIfNeeded:function(){if(n)return;n=!0;const e=document.createElement("style");e.appendChild(document.createTextNode('body::before { content: ""; position: fixed; top: 0; right: 0; bottom: 0; left: 0; pointer-events: none; z-index: 2147483647; /* mondaySdk css - can be disabled with: mondaySdk({withoutScrollHelper: true }) */ }')),(document.head||document.getElementsByTagName("head")[0]).appendChild(e)}}},function(e,t){let n=!1;e.exports={initBackgroundTracking:e=>{if(n)return;n=!0;const t=()=>{e.track("ping")};t(),setInterval(t,3e5)}}},function(e){e.exports=JSON.parse('{"name":"monday-sdk-js","version":"0.1.6","private":false,"repository":"https://github.com/mondaycom/monday-sdk-js","main":"src/index.js","author":"talharamati <tal@monday.com>","license":"MIT","files":["LICENSE","README.md","dist/","src/","server-sdk.js"],"dependencies":{"@types/source-map":"^0.5.2","node-fetch":"^2.6.0"},"devDependencies":{"@babel/cli":"^7.6.0","@babel/core":"^7.6.0","@babel/node":"^7.6.1","@babel/preset-env":"^7.6.0","@babel/preset-react":"^7.0.0","@babel/register":"^7.6.0","babel-loader":"^8.0.6","chai":"^4.2.0","eslint":"^6.8.0","jsdom":"^16.2.0","mocha":"^7.1.0","prettier":"^1.19.1","sinon":"^9.0.0","sinon-chai":"^3.5.0","webpack":"^4.38.0","webpack-cli":"^3.3.6","webpack-dev-server":"^3.7.2"},"scripts":{"start":"webpack-dev-server","build":"webpack --mode=production --env.WEBPACK_BUILD=true","test":"mocha \'./src/**/*-test.js\'","test:watch":"mocha \'./src/**/*-test.js\' --watch","precommit":"yarn lint && yarn style-check","lint":"eslint \'./src/**/*.*\'","style-check":"prettier --check \'./src/**/*.js\'"}}')},function(e,t,n){const r=n(1),{oauthToken:i}=n(13);class o{constructor(e={}){this._token=e.token,this.setToken=this.setToken.bind(this),this.api=this.api.bind(this)}setToken(e){this._token=e}async api(e,t={}){const n={query:e,variables:t.variables},i=t.token||this._token;if(!i)throw new Error("Should send 'token' as an option or call mondaySdk.setToken(TOKEN)");return await r.execute(n,i)}oauthToken(e,t,n){return i(e,t,n)}}e.exports=function(e={}){return new o(e)}},function(e,t,n){const{execute:r}=n(1),{MONDAY_OAUTH_TOKEN_URL:i}=n(2);e.exports={oauthToken:(e,t,n)=>r({code:e,client_id:t,client_secret:n},null,{url:i})}}]);
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "monday-sdk-js",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "repository": "https://github.com/mondaycom/monday-sdk-js",
6
6
  "main": "src/index.js",
7
+ "types": "types/index.d.ts",
7
8
  "author": "talharamati <tal@monday.com>",
8
9
  "license": "MIT",
9
10
  "files": [
@@ -14,7 +15,6 @@
14
15
  "server-sdk.js"
15
16
  ],
16
17
  "dependencies": {
17
- "@types/source-map": "^0.5.2",
18
18
  "node-fetch": "^2.6.0"
19
19
  },
20
20
  "devDependencies": {
@@ -34,7 +34,9 @@
34
34
  "sinon-chai": "^3.5.0",
35
35
  "webpack": "^4.38.0",
36
36
  "webpack-cli": "^3.3.6",
37
- "webpack-dev-server": "^3.7.2"
37
+ "webpack-dev-server": "^3.7.2",
38
+ "@types/source-map": "^0.5.2",
39
+ "typescript": "^4.9.5"
38
40
  },
39
41
  "scripts": {
40
42
  "start": "webpack-dev-server",
@@ -43,6 +45,7 @@
43
45
  "test:watch": "mocha './src/**/*-test.js' --watch",
44
46
  "precommit": "yarn lint && yarn style-check",
45
47
  "lint": "eslint './src/**/*.*'",
46
- "style-check": "prettier --check './src/**/*.js'"
48
+ "style-check": "prettier --check './src/**/*.js'",
49
+ "compile-types": "tsc --noEmit"
47
50
  }
48
51
  }
@@ -87,6 +87,22 @@ describe("Monday Client Test", () => {
87
87
  clock.tick(5);
88
88
  expect(listenCallback).to.be.calledWithExactly(data);
89
89
  });
90
+
91
+ it("unsubscribe should prevent callback being called", () => {
92
+ const data = {
93
+ method: "method",
94
+ type,
95
+ requestId: "requestId"
96
+ };
97
+ const unsubscribe = mondayClient.listen(type, listenCallback);
98
+ window.postMessage(data, "*");
99
+ window.postMessage(data, "*");
100
+ window.postMessage(data, "*");
101
+ clock.tick(5);
102
+ unsubscribe();
103
+ window.postMessage(data, "*");
104
+ expect(listenCallback).to.be.calledWithExactly(data).and.calledThrice;
105
+ });
90
106
  });
91
107
  describe("api methods", () => {
92
108
  let postMessageStub;
@@ -105,17 +121,6 @@ describe("Monday Client Test", () => {
105
121
  expect(postMessageStub).to.be.called;
106
122
  window.removeEventListener("message", postMessageStub, false);
107
123
  });
108
- it("should add a listener to the listener array with the key of ", () => {
109
- let requestId;
110
- function onPostMessage(event) {
111
- requestId = event.data.requestId;
112
- }
113
- window.addEventListener("message", onPostMessage, false);
114
- mondayClient.api("query");
115
- clock.tick(5);
116
- expect(mondayClient.listeners[requestId]).to.be.ok;
117
- window.removeEventListener("message", onPostMessage, false);
118
- });
119
124
 
120
125
  it("get api post message", () => {
121
126
  window.addEventListener("message", postMessageStub, false);
package/src/client.js CHANGED
@@ -64,11 +64,16 @@ class MondayClientSdk {
64
64
 
65
65
  listen(typeOrTypes, callback, params) {
66
66
  const types = convertToArrayIfNeeded(typeOrTypes);
67
+ const unsubscribes = [];
68
+
67
69
  types.forEach(type => {
68
- this._addListener(type, callback);
70
+ unsubscribes.push(this._addListener(type, callback));
69
71
  this._localApi("listen", { type, params });
70
72
  });
71
- // todo uniq listeners, remove listener
73
+
74
+ return () => {
75
+ unsubscribes.forEach(unsubscribe => unsubscribe());
76
+ };
72
77
  }
73
78
 
74
79
  get(type, params) {
@@ -117,7 +122,8 @@ class MondayClientSdk {
117
122
  const version = pjson.version;
118
123
 
119
124
  window.parent.postMessage({ method, args, requestId, clientId, version }, "*");
120
- this._addListener(requestId, data => {
125
+ const removeListener = this._addListener(requestId, data => {
126
+ removeListener();
121
127
  if (data.errorMessage) {
122
128
  const error = new Error(data.errorMessage);
123
129
  error.data = data.data;
@@ -134,7 +140,7 @@ class MondayClientSdk {
134
140
  const methodListeners = this.listeners[method] || EMPTY_ARRAY;
135
141
  const typeListeners = this.listeners[type] || EMPTY_ARRAY;
136
142
  const requestIdListeners = this.listeners[requestId] || EMPTY_ARRAY;
137
- let listeners = [...methodListeners, ...typeListeners, ...requestIdListeners];
143
+ let listeners = new Set([...methodListeners, ...typeListeners, ...requestIdListeners]);
138
144
 
139
145
  if (listeners) {
140
146
  listeners.forEach(listener => {
@@ -148,14 +154,21 @@ class MondayClientSdk {
148
154
  }
149
155
 
150
156
  _addListener(key, callback) {
151
- this.listeners[key] = this.listeners[key] || [];
152
- this.listeners[key].push(callback);
157
+ this.listeners[key] = this.listeners[key] || new Set();
158
+ this.listeners[key].add(callback);
159
+
160
+ return () => {
161
+ this.listeners[key].delete(callback);
162
+ if (this.listeners[key].size === 0) {
163
+ delete this.listeners[key];
164
+ }
165
+ };
153
166
  }
154
167
 
155
168
  _generateRequestId() {
156
169
  return Math.random()
157
170
  .toString(36)
158
- .substr(2, 9);
171
+ .substring(2, 9);
159
172
  }
160
173
 
161
174
  _removeEventListener() {