pulsar-client 1.12.0 → 1.13.0-rc.3

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.
@@ -83,7 +83,7 @@ jobs:
83
83
  npx node-pre-gyp package --target_arch=${{ matrix.arch }}
84
84
 
85
85
  - name: Upload artifacts
86
- uses: actions/upload-artifact@v3
86
+ uses: actions/upload-artifact@v4
87
87
  with:
88
88
  name: macos-${{matrix.nodejs}}-${{matrix.arch}}
89
89
  path: build/stage/*/*.tar.gz
@@ -135,7 +135,7 @@ jobs:
135
135
  /pulsar-client-node/pkg/linux/build-napi-inside-docker.sh
136
136
 
137
137
  - name: Upload artifacts
138
- uses: actions/upload-artifact@v3
138
+ uses: actions/upload-artifact@v4
139
139
  with:
140
140
  name: ${{matrix.image}}-${{matrix.nodejs}}-${{matrix.cpu.platform}}
141
141
  path: build/stage/*/*.tar.gz
@@ -205,7 +205,7 @@ jobs:
205
205
  npx node-pre-gyp package --target_arch=${{ env.TARGET }}
206
206
 
207
207
  - name: Upload artifacts
208
- uses: actions/upload-artifact@v3
208
+ uses: actions/upload-artifact@v4
209
209
  with:
210
210
  name: windows-${{matrix.nodejs}}-${{matrix.arch}}
211
211
  path: build/stage/*/*.tar.gz
@@ -17,8 +17,12 @@
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
19
  #
20
-
21
- import sys, json, urllib.request, os, shutil, zipfile, tempfile
20
+ import sys
21
+ import requests
22
+ import os
23
+ import shutil
24
+ import zipfile
25
+ import tempfile
22
26
  from pathlib import Path
23
27
 
24
28
  if len(sys.argv) != 3:
@@ -39,36 +43,35 @@ workflow_run_id = int(sys.argv[1])
39
43
  dest_path = sys.argv[2]
40
44
 
41
45
  workflow_run_url = LIST_URL % workflow_run_id
42
- request = urllib.request.Request(workflow_run_url,
43
- headers={'Accept': ACCEPT_HEADER, 'Authorization': 'Bearer ' + GITHUB_TOKEN})
44
- with urllib.request.urlopen(request) as response:
45
- data = json.loads(response.read().decode("utf-8"))
46
- for artifact in data['artifacts']:
47
- name = artifact['name']
48
- url = artifact['archive_download_url']
46
+ headers = {'Accept': ACCEPT_HEADER, 'Authorization': f'Bearer {GITHUB_TOKEN}'}
49
47
 
50
- print('Downloading %s from %s' % (name, url))
51
- artifact_request = urllib.request.Request(url,
52
- headers={'Authorization': 'Bearer ' + GITHUB_TOKEN})
53
- with urllib.request.urlopen(artifact_request) as response:
54
- tmp_zip = tempfile.NamedTemporaryFile(delete=False)
55
- try:
56
- #
57
- shutil.copyfileobj(response, tmp_zip)
58
- tmp_zip.close()
48
+ response = requests.get(workflow_run_url, headers=headers)
49
+ response.raise_for_status()
59
50
 
60
- dest_dir = os.path.join(dest_path, name)
61
- Path(dest_dir).mkdir(parents=True, exist_ok=True)
62
- with zipfile.ZipFile(tmp_zip.name, 'r') as z:
63
- z.extractall(dest_dir)
64
- finally:
65
- os.unlink(tmp_zip.name)
51
+ data = response.json()
52
+ for artifact in data['artifacts']:
53
+ name = artifact['name']
54
+ url = artifact['archive_download_url']
66
55
 
67
- for root, dirs, files in os.walk(dest_path, topdown=False):
68
- for name in files:
69
- shutil.move(os.path.join(root, name), dest_path)
70
- if not os.listdir(root):
71
- os.rmdir(root)
56
+ print(f'Downloading {name} from {url}')
57
+ artifact_response = requests.get(url, headers=headers, stream=True)
58
+ artifact_response.raise_for_status()
72
59
 
60
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_zip:
61
+ for chunk in artifact_response.iter_content(chunk_size=8192):
62
+ tmp_zip.write(chunk)
63
+ tmp_zip_path = tmp_zip.name
73
64
 
65
+ try:
66
+ dest_dir = os.path.join(dest_path, name)
67
+ Path(dest_dir).mkdir(parents=True, exist_ok=True)
68
+ with zipfile.ZipFile(tmp_zip_path, 'r') as z:
69
+ z.extractall(dest_dir)
70
+ finally:
71
+ os.unlink(tmp_zip_path)
74
72
 
73
+ for root, dirs, files in os.walk(dest_path, topdown=False):
74
+ for name in files:
75
+ shutil.move(os.path.join(root, name), dest_path)
76
+ if not os.listdir(root):
77
+ os.rmdir(root)
@@ -20,7 +20,7 @@
20
20
 
21
21
  set -e -x
22
22
 
23
- if [ $# -neq 2 ]; then
23
+ if [ "$#" -ne 2 ]; then
24
24
  echo "Usage: $0 \$DEST_PATH \$WORKFLOW_ID"
25
25
  exit 1
26
26
  fi
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ const Pulsar = require('../');
21
+
22
+ (async () => {
23
+ // Create a client
24
+ const client = new Pulsar.Client({
25
+ serviceUrl: 'pulsar://localhost:6650',
26
+ operationTimeoutSeconds: 30,
27
+ });
28
+
29
+ const schemaInfo = {
30
+ schemaType: "Json",
31
+ schema: JSON.stringify({
32
+ type: 'record',
33
+ name: 'Example',
34
+ namespace: 'test',
35
+ fields: [
36
+ {
37
+ name: 'a',
38
+ type: 'int',
39
+ },
40
+ {
41
+ name: 'b',
42
+ type: 'int',
43
+ },
44
+ ],
45
+ }),
46
+ };
47
+
48
+ // Create a consumer
49
+ const consumer = await client.subscribe({
50
+ topic: 'persistent://public/default/schema-test',
51
+ subscription: 'sub1',
52
+ subscriptionType: 'Shared',
53
+ ackTimeoutMs: 10000,
54
+ schema: schemaInfo,
55
+ });
56
+
57
+ // Receive messages
58
+ for (let i = 0; i < 10; i += 1) {
59
+ const msg = await consumer.receive();
60
+ console.log(msg.getData().toString());
61
+ consumer.acknowledge(msg);
62
+ }
63
+
64
+ await consumer.close();
65
+ await client.close();
66
+ })();
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ const { exit } = require('process');
21
+ const Pulsar = require('../');
22
+ console.log("Starting consumer");
23
+ (async () => {
24
+
25
+ const auth = new Pulsar.AuthenticationToken({token: "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtypruRC4fb1DWtoLL62SY"});
26
+
27
+ // Create a client
28
+ const client = new Pulsar.Client({
29
+ serviceUrl: 'pulsar://localhost:6650',
30
+ authentication: auth,
31
+ });
32
+
33
+ // Create a consumer
34
+ const consumer = await client.subscribe({
35
+ topic: 'persistent://public/default/my-topic',
36
+ subscription: 'sub1',
37
+ subscriptionType: 'Shared',
38
+ ackTimeoutMs: 10000,
39
+ });
40
+
41
+ // Receive messages
42
+ for (let i = 0; i < 10; i += 1) {
43
+ const msg = await consumer.receive();
44
+ console.log(msg.getData().toString());
45
+ consumer.acknowledge(msg);
46
+ }
47
+
48
+ await consumer.close();
49
+ await client.close();
50
+ })();
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ const { exit } = require('process');
21
+ const Pulsar = require('..');
22
+ console.log("Starting consumer");
23
+
24
+ async function getToken() {
25
+ console.log("Get token");
26
+ return "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtypruRC4fb1DWtoLL62SY";
27
+ }
28
+
29
+ (async () => {
30
+
31
+ const auth = new Pulsar.AuthenticationToken({token: getToken});
32
+
33
+ // Create a client
34
+ const client = new Pulsar.Client({
35
+ serviceUrl: 'pulsar://localhost:6650',
36
+ authentication: auth,
37
+ });
38
+
39
+ // Create a consumer
40
+ const consumer = await client.subscribe({
41
+ topic: 'persistent://public/default/my-topic',
42
+ subscription: 'sub1',
43
+ subscriptionType: 'Shared',
44
+ ackTimeoutMs: 10000,
45
+ });
46
+
47
+ // Receive messages
48
+ for (let i = 0; i < 10; i += 1) {
49
+ const msg = await consumer.receive();
50
+ console.log(msg.getData().toString());
51
+ consumer.acknowledge(msg);
52
+ }
53
+
54
+ await consumer.close();
55
+ await client.close();
56
+ })();
@@ -24,10 +24,10 @@ const Pulsar = require('..');
24
24
  const client = new Pulsar.Client({
25
25
  serviceUrl: 'pulsar://localhost:6650',
26
26
  operationTimeoutSeconds: 30,
27
- });
28
-
29
- Pulsar.Client.setLogHandler((level, file, line, message) => {
30
- console.log('[%s][%s:%d] %s', Pulsar.LogLevel.toString(level), file, line, message);
27
+ log: (level, file, line, message) => {
28
+ console.log('[%s][%s:%d] %s', Pulsar.LogLevel.toString(level), file, line, message);
29
+ },
30
+ logLevel: Pulsar.LogLevel.DEBUG,
31
31
  });
32
32
 
33
33
  // Create a producer
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ const Pulsar = require('../');
21
+
22
+ (async () => {
23
+
24
+ // Create a client
25
+ const client = new Pulsar.Client({
26
+ serviceUrl: 'pulsar://localhost:6650',
27
+ operationTimeoutSeconds: 30,
28
+ });
29
+
30
+ const schemaInfo = {
31
+ schemaType: "Json",
32
+ schema: JSON.stringify({
33
+ type: 'record',
34
+ name: 'Example',
35
+ namespace: 'test',
36
+ fields: [
37
+ {
38
+ name: 'a',
39
+ type: 'int',
40
+ },
41
+ {
42
+ name: 'b',
43
+ type: 'int',
44
+ },
45
+ ],
46
+ }),
47
+ };
48
+
49
+ const message = {
50
+ a: 1,
51
+ b: 2,
52
+ };
53
+
54
+ // Create a producer
55
+ const producer = await client.createProducer({
56
+ topic: 'persistent://public/default/schema-test',
57
+ sendTimeoutMs: 30000,
58
+ batchingEnabled: true,
59
+ schema: schemaInfo,
60
+ });
61
+
62
+ // Send messages
63
+ for (let i = 0; i < 100; i += 1) {
64
+ message.a = i;
65
+ message.b = i * 2;
66
+ const data = JSON.stringify(message);
67
+ producer.send({
68
+ data: Buffer.from(data),
69
+ });
70
+ console.log(`Sent message: ${data}`);
71
+ }
72
+ await producer.flush();
73
+
74
+ await producer.close();
75
+ await client.close();;
76
+ })();
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ const Pulsar = require('../');
21
+
22
+ (async () => {
23
+ const auth = new Pulsar.AuthenticationToken({token: "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtypruRC4fb1DWtoLL62SY"});
24
+
25
+ // Create a client
26
+ const client = new Pulsar.Client({
27
+ serviceUrl: 'pulsar://localhost:6650',
28
+ authentication: auth,
29
+ });
30
+
31
+ // Create a producer
32
+ const producer = await client.createProducer({
33
+ topic: 'persistent://public/default/my-topic',
34
+ });
35
+
36
+ // Send messages
37
+ for (let i = 0; i < 10; i += 1) {
38
+ const msg = `my-message-${i}`;
39
+ producer.send({
40
+ data: Buffer.from(msg),
41
+ });
42
+ console.log(`Sent message: ${msg}`);
43
+ }
44
+ await producer.flush();
45
+
46
+ await producer.close();
47
+ await client.close();
48
+ })();
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ const Pulsar = require('..');
21
+
22
+ async function getToken() {
23
+ console.log("Get token");
24
+ return "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.ipevRNuRP6HflG8cFKnmUPtypruRC4fb1DWtoLL62SY";
25
+ }
26
+
27
+ (async () => {
28
+ const auth = new Pulsar.AuthenticationToken({token: getToken});
29
+
30
+ // Create a client
31
+ const client = new Pulsar.Client({
32
+ serviceUrl: 'pulsar://localhost:6650',
33
+ authentication: auth,
34
+ });
35
+
36
+ // Create a producer
37
+ const producer = await client.createProducer({
38
+ topic: 'persistent://public/default/my-topic',
39
+ });
40
+
41
+ // Send messages
42
+ for (let i = 0; i < 10; i += 1) {
43
+ const msg = `my-message-${i}`;
44
+ producer.send({
45
+ data: Buffer.from(msg),
46
+ });
47
+ console.log(`Sent message: ${msg}`);
48
+ }
49
+ await producer.flush();
50
+
51
+ await producer.close();
52
+ await client.close();
53
+ })();
package/index.d.ts CHANGED
@@ -32,6 +32,7 @@ export interface ClientConfig {
32
32
  statsIntervalInSeconds?: number;
33
33
  listenerName?: string;
34
34
  log?: (level: LogLevel, file: string, line: number, message: string) => void;
35
+ logLevel?: LogLevel;
35
36
  }
36
37
 
37
38
  export class Client {
@@ -224,7 +225,7 @@ export class AthenzX509Config {
224
225
  }
225
226
 
226
227
  export class AuthenticationToken {
227
- constructor(params: { token: string });
228
+ constructor(params: { token: string | (() => string) | (() => Promise<string>) });
228
229
  }
229
230
 
230
231
  export class AuthenticationOauth2 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulsar-client",
3
- "version": "1.12.0",
3
+ "version": "1.13.0-rc.3",
4
4
  "description": "Pulsar Node.js client",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -1,2 +1,2 @@
1
- CPP_CLIENT_BASE_URL=https://archive.apache.org/dist/pulsar/pulsar-client-cpp-3.6.0
2
- CPP_CLIENT_VERSION=3.6.0
1
+ CPP_CLIENT_BASE_URL=https://archive.apache.org/dist/pulsar/pulsar-client-cpp-3.7.0
2
+ CPP_CLIENT_VERSION=3.7.0
@@ -18,6 +18,7 @@
18
18
  */
19
19
 
20
20
  #include "Authentication.h"
21
+ #include <future>
21
22
 
22
23
  static const std::string PARAM_TLS_CERT = "certificatePath";
23
24
  static const std::string PARAM_TLS_KEY = "privateKeyPath";
@@ -25,6 +26,62 @@ static const std::string PARAM_TOKEN = "token";
25
26
  static const std::string PARAM_USERNAME = "username";
26
27
  static const std::string PARAM_PASSWORD = "password";
27
28
 
29
+ void FinalizeTokenSupplierCallback(Napi::Env env, TokenSupplierCallback *cb, void *) { delete cb; }
30
+
31
+ struct TokenSupplierProxyData {
32
+ std::function<void(void)> callback;
33
+ std::string token;
34
+
35
+ TokenSupplierProxyData(std::function<void(void)> callback) : callback(callback), token(std::string()) {}
36
+ };
37
+
38
+ void TokenSupplierProxy(Napi::Env env, Napi::Function jsCallback, TokenSupplierProxyData *data) {
39
+ Napi::Value ret = jsCallback.Call({});
40
+ if (ret.IsPromise()) {
41
+ Napi::Promise promise = ret.As<Napi::Promise>();
42
+ Napi::Value thenValue = promise.Get("then");
43
+ if (thenValue.IsFunction()) {
44
+ Napi::Function then = thenValue.As<Napi::Function>();
45
+ Napi::Function callback = Napi::Function::New(env, [data](const Napi::CallbackInfo &info) {
46
+ Napi::Value value = info[0];
47
+ if (value.IsString()) {
48
+ data->token = value.ToString().Utf8Value();
49
+ }
50
+ data->callback();
51
+ });
52
+ then.Call(promise, {callback});
53
+ return;
54
+ }
55
+ }
56
+ if (ret.IsString()) {
57
+ data->token = ret.ToString().Utf8Value();
58
+ }
59
+ data->callback();
60
+ }
61
+
62
+ char *TokenSupplier(void *ctx) {
63
+ TokenSupplierCallback *tokenSupplierCallback = (TokenSupplierCallback *)ctx;
64
+ if (tokenSupplierCallback->callback.Acquire() != napi_ok) {
65
+ char *empty = (char *)malloc(0);
66
+ return empty;
67
+ }
68
+
69
+ std::promise<void> promise;
70
+ std::future<void> future = promise.get_future();
71
+
72
+ std::unique_ptr<TokenSupplierProxyData> dataPtr(
73
+ new TokenSupplierProxyData([&promise]() { promise.set_value(); }));
74
+
75
+ tokenSupplierCallback->callback.BlockingCall(dataPtr.get(), TokenSupplierProxy);
76
+ tokenSupplierCallback->callback.Release();
77
+
78
+ future.wait();
79
+
80
+ char *token = (char *)malloc(dataPtr->token.size());
81
+ strcpy(token, dataPtr->token.c_str());
82
+ return token;
83
+ }
84
+
28
85
  Napi::FunctionReference Authentication::constructor;
29
86
 
30
87
  Napi::Object Authentication::Init(Napi::Env env, Napi::Object exports) {
@@ -69,12 +126,28 @@ Authentication::Authentication(const Napi::CallbackInfo &info)
69
126
  pulsar_authentication_tls_create(obj.Get(PARAM_TLS_CERT).ToString().Utf8Value().c_str(),
70
127
  obj.Get(PARAM_TLS_KEY).ToString().Utf8Value().c_str());
71
128
  } else if (authMethod == "token") {
72
- if (!obj.Has(PARAM_TOKEN) || !obj.Get(PARAM_TOKEN).IsString()) {
73
- Napi::Error::New(env, "Missing required parameter").ThrowAsJavaScriptException();
74
- return;
129
+ if (obj.Has(PARAM_TOKEN)) {
130
+ if (obj.Get(PARAM_TOKEN).IsString()) {
131
+ this->cAuthentication =
132
+ pulsar_authentication_token_create(obj.Get(PARAM_TOKEN).ToString().Utf8Value().c_str());
133
+ return;
134
+ }
135
+
136
+ if (obj.Get(PARAM_TOKEN).IsFunction()) {
137
+ this->tokenSupplier = new TokenSupplierCallback();
138
+ Napi::ThreadSafeFunction callback = Napi::ThreadSafeFunction::New(
139
+ obj.Env(), obj.Get(PARAM_TOKEN).As<Napi::Function>(), "Token Supplier Callback", 1, 1,
140
+ (void *)NULL, FinalizeTokenSupplierCallback, this->tokenSupplier);
141
+ this->tokenSupplier->callback = std::move(callback);
142
+
143
+ this->cAuthentication =
144
+ pulsar_authentication_token_create_with_supplier(&TokenSupplier, this->tokenSupplier);
145
+ return;
146
+ }
75
147
  }
76
- this->cAuthentication =
77
- pulsar_authentication_token_create(obj.Get(PARAM_TOKEN).ToString().Utf8Value().c_str());
148
+
149
+ Napi::Error::New(env, "Missing required parameter").ThrowAsJavaScriptException();
150
+ return;
78
151
  } else if (authMethod == "basic") {
79
152
  if (!obj.Has(PARAM_USERNAME) || !obj.Get(PARAM_USERNAME).IsString() || !obj.Has(PARAM_PASSWORD) ||
80
153
  !obj.Get(PARAM_PASSWORD).IsString()) {
@@ -105,6 +178,9 @@ Authentication::Authentication(const Napi::CallbackInfo &info)
105
178
  }
106
179
 
107
180
  Authentication::~Authentication() {
181
+ if (this->tokenSupplier != nullptr) {
182
+ this->tokenSupplier->callback.Release();
183
+ }
108
184
  if (this->cAuthentication != nullptr) {
109
185
  pulsar_authentication_free(this->cAuthentication);
110
186
  }
@@ -22,6 +22,7 @@
22
22
 
23
23
  #include <napi.h>
24
24
  #include <pulsar/c/authentication.h>
25
+ #include "TokenSupplier.h"
25
26
 
26
27
  class Authentication : public Napi::ObjectWrap<Authentication> {
27
28
  public:
@@ -33,6 +34,8 @@ class Authentication : public Napi::ObjectWrap<Authentication> {
33
34
  private:
34
35
  static Napi::FunctionReference constructor;
35
36
  pulsar_authentication_t *cAuthentication;
37
+
38
+ TokenSupplierCallback *tokenSupplier;
36
39
  };
37
40
 
38
41
  #endif
package/src/Client.cc CHANGED
@@ -40,6 +40,7 @@ static const std::string CFG_TLS_VALIDATE_HOSTNAME = "tlsValidateHostname";
40
40
  static const std::string CFG_TLS_ALLOW_INSECURE = "tlsAllowInsecureConnection";
41
41
  static const std::string CFG_STATS_INTERVAL = "statsIntervalInSeconds";
42
42
  static const std::string CFG_LOG = "log";
43
+ static const std::string CFG_LOG_LEVEL = "logLevel";
43
44
  static const std::string CFG_LISTENER_NAME = "listenerName";
44
45
 
45
46
  LogCallback *Client::logCallback = nullptr;
@@ -107,7 +108,18 @@ Client::Client(const Napi::CallbackInfo &info) : Napi::ObjectWrap<Client>(info)
107
108
  pulsar_client_configuration_free);
108
109
 
109
110
  // The logger can only be set once per process, so we will take control of it
110
- pulsar_client_configuration_set_logger(cClientConfig.get(), &LogMessage, nullptr);
111
+ if (clientConfig.Has(CFG_LOG_LEVEL) && clientConfig.Get(CFG_LOG_LEVEL).IsNumber()) {
112
+ int32_t logLevelInt = clientConfig.Get(CFG_LOG_LEVEL).ToNumber().Int32Value();
113
+ this->logLevel = static_cast<pulsar_logger_level_t>(logLevelInt);
114
+ }
115
+ pulsar_logger_t logger;
116
+ logger.ctx = &this->logLevel;
117
+ logger.is_enabled = [](pulsar_logger_level_t level, void *ctx) {
118
+ auto *logLevel = static_cast<pulsar_logger_level_t *>(ctx);
119
+ return level >= *logLevel;
120
+ };
121
+ logger.log = &LogMessage;
122
+ pulsar_client_configuration_set_logger_t(cClientConfig.get(), logger);
111
123
 
112
124
  // log config option should be deprecated in favour of static setLogHandler method
113
125
  if (clientConfig.Has(CFG_LOG) && clientConfig.Get(CFG_LOG).IsFunction()) {
package/src/Client.h CHANGED
@@ -53,6 +53,7 @@ class Client : public Napi::ObjectWrap<Client> {
53
53
  static Napi::FunctionReference constructor;
54
54
  std::shared_ptr<pulsar_client_t> cClient;
55
55
  std::shared_ptr<pulsar_client_configuration_t> cClientConfig;
56
+ pulsar_logger_level_t logLevel = pulsar_logger_level_t::pulsar_INFO;
56
57
 
57
58
  Napi::Value CreateProducer(const Napi::CallbackInfo &info);
58
59
  Napi::Value Subscribe(const Napi::CallbackInfo &info);
package/src/Consumer.cc CHANGED
@@ -77,32 +77,28 @@ void MessageListenerProxy(Napi::Env env, Napi::Function jsCallback, MessageListe
77
77
  Napi::Object msg = Message::NewInstance({}, data->cMessage);
78
78
  Consumer *consumer = data->consumer;
79
79
 
80
- // `consumer` might be null in certain cases, segmentation fault might happend without this null check. We
81
- // need to handle this rare case in future.
82
- if (consumer) {
83
- Napi::Value ret;
84
- try {
85
- ret = jsCallback.Call({msg, consumer->Value()});
86
- } catch (std::exception &exception) {
87
- logMessageListenerError(consumer, exception.what());
88
- }
80
+ Napi::Value ret;
81
+ try {
82
+ ret = jsCallback.Call({msg, consumer->Value()});
83
+ } catch (std::exception &exception) {
84
+ logMessageListenerError(consumer, exception.what());
85
+ }
89
86
 
90
- if (ret.IsPromise()) {
91
- Napi::Promise promise = ret.As<Napi::Promise>();
92
- Napi::Function catchFunc = promise.Get("catch").As<Napi::Function>();
87
+ if (ret.IsPromise()) {
88
+ Napi::Promise promise = ret.As<Napi::Promise>();
89
+ Napi::Function catchFunc = promise.Get("catch").As<Napi::Function>();
93
90
 
94
- ret = catchFunc.Call(promise, {Napi::Function::New(env, [consumer](const Napi::CallbackInfo &info) {
95
- Napi::Error error = info[0].As<Napi::Error>();
96
- logMessageListenerError(consumer, error.what());
97
- })});
91
+ ret = catchFunc.Call(promise, {Napi::Function::New(env, [consumer](const Napi::CallbackInfo &info) {
92
+ Napi::Error error = info[0].As<Napi::Error>();
93
+ logMessageListenerError(consumer, error.what());
94
+ })});
98
95
 
99
- promise = ret.As<Napi::Promise>();
100
- Napi::Function finallyFunc = promise.Get("finally").As<Napi::Function>();
96
+ promise = ret.As<Napi::Promise>();
97
+ Napi::Function finallyFunc = promise.Get("finally").As<Napi::Function>();
101
98
 
102
- finallyFunc.Call(
103
- promise, {Napi::Function::New(env, [data](const Napi::CallbackInfo &info) { data->callback(); })});
104
- return;
105
- }
99
+ finallyFunc.Call(
100
+ promise, {Napi::Function::New(env, [data](const Napi::CallbackInfo &info) { data->callback(); })});
101
+ return;
106
102
  }
107
103
  data->callback();
108
104
  }
@@ -111,7 +107,7 @@ void MessageListener(pulsar_consumer_t *rawConsumer, pulsar_message_t *rawMessag
111
107
  std::shared_ptr<pulsar_message_t> cMessage(rawMessage, pulsar_message_free);
112
108
  MessageListenerCallback *listenerCallback = (MessageListenerCallback *)ctx;
113
109
 
114
- Consumer *consumer = (Consumer *)listenerCallback->consumer;
110
+ Consumer *consumer = static_cast<Consumer *>(listenerCallback->consumerFuture.get());
115
111
 
116
112
  if (listenerCallback->callback.Acquire() != napi_ok) {
117
113
  return;
@@ -135,7 +131,7 @@ void Consumer::SetListenerCallback(MessageListenerCallback *listener) {
135
131
  }
136
132
 
137
133
  if (listener != nullptr) {
138
- listener->consumer = this;
134
+ listener->consumerPromise.set_value(this);
139
135
  // If a consumer listener is set, the Consumer instance is kept alive even if it goes out of scope in JS
140
136
  // code.
141
137
  this->Ref();
@@ -168,11 +164,6 @@ struct ConsumerNewInstanceContext {
168
164
  auto cConsumer = std::shared_ptr<pulsar_consumer_t>(rawConsumer, pulsar_consumer_free);
169
165
  auto listener = consumerConfig->GetListenerCallback();
170
166
 
171
- if (listener) {
172
- // pause, will resume in OnOK, to prevent MessageListener get a nullptr of consumer
173
- pulsar_consumer_pause_message_listener(cConsumer.get());
174
- }
175
-
176
167
  deferred->Resolve([cConsumer, consumerConfig, listener](const Napi::Env env) {
177
168
  Napi::Object obj = Consumer::constructor.New({});
178
169
  Consumer *consumer = Consumer::Unwrap(obj);
@@ -180,11 +171,6 @@ struct ConsumerNewInstanceContext {
180
171
  consumer->SetCConsumer(cConsumer);
181
172
  consumer->SetListenerCallback(listener);
182
173
 
183
- if (listener) {
184
- // resume to enable MessageListener function callback
185
- resume_message_listener(cConsumer.get());
186
- }
187
-
188
174
  return obj;
189
175
  });
190
176
  }
@@ -21,14 +21,16 @@
21
21
  #define MESSAGELISTENER_H
22
22
 
23
23
  #include <napi.h>
24
+ #include <future>
24
25
 
25
26
  struct MessageListenerCallback {
26
27
  Napi::ThreadSafeFunction callback;
27
28
 
28
- // Using consumer as void* since the ListenerCallback is shared between Config and Consumer.
29
- void *consumer;
29
+ // Use future store consumer point, because need ensure sync.
30
+ std::promise<void *> consumerPromise;
31
+ std::shared_future<void *> consumerFuture;
30
32
 
31
- MessageListenerCallback() : consumer(nullptr) {}
33
+ MessageListenerCallback() : consumerPromise(), consumerFuture(consumerPromise.get_future()) {}
32
34
  };
33
35
 
34
36
  #endif
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ #ifndef PULSAR_CLIENT_NODE_TOKENSUPPLIER_H
21
+ #define PULSAR_CLIENT_NODE_TOKENSUPPLIER_H
22
+
23
+ #include <napi.h>
24
+
25
+ struct TokenSupplierCallback {
26
+ Napi::ThreadSafeFunction callback;
27
+
28
+ TokenSupplierCallback() {}
29
+ };
30
+
31
+ #endif // PULSAR_CLIENT_NODE_TOKENSUPPLIER_H
@@ -1284,5 +1284,49 @@ const Pulsar = require('../index');
1284
1284
  await consumer.close();
1285
1285
  await client.close();
1286
1286
  });
1287
+
1288
+ test('AuthenticationToken token supplier', async () => {
1289
+ const mockTokenSupplier = jest.fn().mockReturnValue('token');
1290
+ const auth = new Pulsar.AuthenticationToken({
1291
+ token: mockTokenSupplier,
1292
+ });
1293
+ const client = new Pulsar.Client({
1294
+ serviceUrl: 'pulsar://localhost:6650',
1295
+ authentication: auth,
1296
+ });
1297
+
1298
+ // A producer/consumer is needed to triger the callback function
1299
+ const topic = 'persistent://public/default/token-auth';
1300
+ const producer = await client.createProducer({
1301
+ topic,
1302
+ });
1303
+ expect(producer).not.toBeNull();
1304
+ expect(mockTokenSupplier).toHaveBeenCalledTimes(1);
1305
+
1306
+ await producer.close();
1307
+ await client.close();
1308
+ });
1309
+
1310
+ test('AuthenticationToken async token supplier', async () => {
1311
+ const mockTokenSupplier = jest.fn().mockResolvedValue('token');
1312
+ const auth = new Pulsar.AuthenticationToken({
1313
+ token: mockTokenSupplier,
1314
+ });
1315
+ const client = new Pulsar.Client({
1316
+ serviceUrl: 'pulsar://localhost:6650',
1317
+ authentication: auth,
1318
+ });
1319
+
1320
+ // A producer/consumer is needed to triger the callback function
1321
+ const topic = 'persistent://public/default/token-auth';
1322
+ const producer = await client.createProducer({
1323
+ topic,
1324
+ });
1325
+ expect(producer).not.toBeNull();
1326
+ expect(mockTokenSupplier).toHaveBeenCalledTimes(1);
1327
+
1328
+ await producer.close();
1329
+ await client.close();
1330
+ });
1287
1331
  });
1288
1332
  })();
package/tstest.ts CHANGED
@@ -67,7 +67,9 @@ import Pulsar = require('./index');
67
67
  });
68
68
 
69
69
  const authToken: Pulsar.AuthenticationToken = new Pulsar.AuthenticationToken({
70
- token: 'foobar',
70
+ token: async () => {
71
+ return 'foobar';
72
+ },
71
73
  });
72
74
 
73
75
  const authBasic: Pulsar.AuthenticationBasic = new Pulsar.AuthenticationBasic({
@@ -1 +0,0 @@
1
- CMakeLists.txt not found in /Users/shibaodi/CLionProjects/pulsar-client-node Select CMakeLists.txt