Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
0c83fb4
pre-requisites for data_sync: handle error array during exceptional c…
May 22, 2026
5502af9
data-sync: channel endpoint apis
May 22, 2026
ea6da83
data-sync:user endpoint apis
May 22, 2026
f128cbc
data-sync:membership endpoint apis
May 22, 2026
fbe3e3c
data-sync: entity endpoint apis
May 22, 2026
8734092
data-sync: entity-relationship endpoint apis
May 22, 2026
1843165
data-sync: apis domain object export, added operation constants, type…
May 22, 2026
7a5a27f
updated dist/lib and codeowner files.
May 22, 2026
e82c07a
fix lint
May 22, 2026
f57123e
Fix CODEOWNERS syntax for jguz-pubnub
mohitpubnub May 22, 2026
3876080
dataSync relationship api for getAll should have class and version in…
May 27, 2026
adbfa29
sync package.json with latest
May 28, 2026
fd1b9e4
fix type definition for getAllRelationships
May 28, 2026
66391b4
Merge branch 'master' into CLEN-3438
mohitpubnub Jun 9, 2026
97152c2
Merge branch 'master' into CLEN-3438
mohitpubnub Jun 25, 2026
4be5002
datasync event handling, grantToken update for datasync permissions
Jul 10, 2026
25eb24d
remove `idempotencyKey` from datasync apis
Jul 21, 2026
f51c1dc
Merge branch 'master' into CLEN-3438
mohitpubnub Jul 21, 2026
6d2d137
lint fixes
Jul 22, 2026
2d6a210
Merge branch 'master' into CLEN-3438
mohitpubnub Jul 23, 2026
c5be680
handling user/channel/membership events in subscription
Jul 23, 2026
8d7f54f
lib files
Jul 23, 2026
7fa7586
dist/lib updates
Jul 23, 2026
2bf1a75
refactor: dataSync apis signature update to improve user experience b…
Jul 27, 2026
0c50646
lint fixes
Jul 27, 2026
82d6083
DATA_SYNC_MODULE environment variable based module load for dataSync …
Jul 27, 2026
be57bd3
Merge branch 'master' into CLEN-3438
mohitpubnub Jul 27, 2026
7e55c5c
dataSync updates: refined method naming convention consistent as per …
mohitpubnub Aug 17, 2026
a35c647
dist and lib files updates
mohitpubnub Aug 17, 2026
25b0586
parseToken to parse users permissions, fix stale test failures of gra…
mohitpubnub Aug 17, 2026
aa34aeb
re-worked update methods added JSDoc for json pointer usage
mohitpubnub Aug 18, 2026
90efcf4
JSDoc with more details on how to use patch (update) apis with json p…
mohitpubnub Aug 19, 2026
f5c5dd4
event listener: unknown event handling - not listener invoked when ev…
mohitpubnub Sep 3, 2026
e02122c
support for dataSync subscribable objects
mohitpubnub Sep 3, 2026
e35271b
build: bump undici version, to address potential vulnerabilities.
mohitpubnub Sep 3, 2026
2e7181f
fix syntax error TS2527 for subscribable type
mohitpubnub Sep 3, 2026
f8425b7
filter parameter renaming for dataSync get apis
mohitpubnub Sep 3, 2026
fe94998
description change for sort param in dataSync
mohitpubnub Sep 3, 2026
6b926d4
TEST(dataSync): channels crud, projection tests added and test helpers.
mohitpubnub Sep 4, 2026
9381d57
refactor(retry-decision): moved status code based retry decision to s…
mohitpubnub Sep 7, 2026
6aa30c3
dataSync error normalised with pubnub-api-error convention, instead c…
mohitpubnub Sep 7, 2026
7131213
PAM extension (refactor): unified permission extraction method at tok…
mohitpubnub Sep 7, 2026
8d41e08
dataSync fixes - getUsers, getChannels, getEntities support class, cl…
mohitpubnub Sep 7, 2026
0309c98
dataSync type definition update: expiresAt is mandatory field receive…
mohitpubnub Sep 7, 2026
80c5f22
test(PAM): access manager test updates with additional validation
mohitpubnub Sep 7, 2026
06385c2
test(dataSync subscription): dataSync entity subscription based refer…
mohitpubnub Sep 7, 2026
954bcbc
test(dataSync) update channels tests using mocked response to speed u…
mohitpubnub Sep 7, 2026
5fdd27d
.pubnub.yml updates for dataSync
mohitpubnub Sep 7, 2026
c801cc8
update configuration as custom origin not needed for dataSync tests
mohitpubnub Sep 7, 2026
7837888
test(fix) for membership error assertion, test fix for projection, co…
mohitpubnub Sep 7, 2026
e17c7b5
test environment variable configuration update
mohitpubnub Sep 7, 2026
9f531f3
test config, dataSync environment configuration
mohitpubnub Sep 7, 2026
b04a75e
reverted run-tests workflow changes, added tests for dataSync events,…
mohitpubnub Sep 7, 2026
5b07221
dist/lib updates
mohitpubnub Sep 7, 2026
bb98219
dataSync parameter naming sync for get*s operation
mohitpubnub Sep 8, 2026
03239d2
dist/lib updates
mohitpubnub Sep 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ env:
PAM_SECRET_KEY: ${{ secrets.SDK_PAM_SEC_KEY }}
SUBSCRIBE_KEY: ${{ secrets.SDK_SUB_KEY }}
PUBLISH_KEY: ${{ secrets.SDK_PUB_KEY }}
DS_SUBSCRIBE_KEY: ${{ secrets.SDK_DS_SUB_KEY }}
DS_PUBLISH_KEY: ${{ secrets.SDK_DS_PUB_KEY }}
DS_SECRET_KEY: ${{ secrets.SDK_DS_SEC_KEY }}

jobs:
tests:
Expand Down
35 changes: 35 additions & 0 deletions .pubnub.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,41 @@ features:
- FILES-DOWNLOAD-FILE
time:
- TIME-TIME
data-sync:
- DATASYNC-USER-CREATE
- DATASYNC-USER-GET
- DATASYNC-USER-GET-ALL
- DATASYNC-USER-UPDATE
- DATASYNC-USER-PATCH
- DATASYNC-USER-DELETE
- DATASYNC-CHANNEL-CREATE
- DATASYNC-CHANNEL-GET
- DATASYNC-CHANNEL-GET-ALL
- DATASYNC-CHANNEL-UPDATE
- DATASYNC-CHANNEL-PATCH
- DATASYNC-CHANNEL-DELETE
- DATASYNC-MEMBERSHIP-CREATE
- DATASYNC-MEMBERSHIP-GET
- DATASYNC-MEMBERSHIP-GET-ALL
- DATASYNC-MEMBERSHIP-UPDATE
- DATASYNC-MEMBERSHIP-PATCH
- DATASYNC-MEMBERSHIP-DELETE
- DATASYNC-ENTITY-CREATE
- DATASYNC-ENTITY-GET
- DATASYNC-ENTITY-GET-ALL
- DATASYNC-ENTITY-UPDATE
- DATASYNC-ENTITY-PATCH
- DATASYNC-ENTITY-DELETE
- DATASYNC-RELATIONSHIP-CREATE
- DATASYNC-RELATIONSHIP-GET
- DATASYNC-RELATIONSHIP-GET-ALL
- DATASYNC-RELATIONSHIP-UPDATE
- DATASYNC-RELATIONSHIP-PATCH
- DATASYNC-RELATIONSHIP-DELETE
- DATASYNC-FILTERING
- DATASYNC-SORTING
- DATASYNC-EVENTS-LISTENER
- DATASYNC-ACCESS-MANAGER
others:
- TELEMETRY
- CREATE-PUSH-PAYLOAD
Expand Down
3,757 changes: 3,444 additions & 313 deletions dist/web/pubnub.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/web/pubnub.min.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions dist/web/pubnub.worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -1861,6 +1861,10 @@
* Request will be sent using `PATCH` method.
*/
TransportMethod["PATCH"] = "PATCH";
/**
* Request will be sent using `PUT` method.
*/
TransportMethod["PUT"] = "PUT";
/**
* Request will be sent using `DELETE` method.
*/
Expand Down
2 changes: 1 addition & 1 deletion dist/web/pubnub.worker.min.js

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions lib/core/components/event-dispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ class EventDispatcher {
set onFile(listener) {
this.updateTypeOrObjectListener({ add: !!listener, listener, type: 'file' });
}
/**
* Set a new DataSync event handler.
*
* @param listener - Listener function, which will be called each time when a new
* DataSync event is received from the real-time network.
*/
set onDataSync(listener) {
this.updateTypeOrObjectListener({ add: !!listener, listener, type: 'dataSync' });
}
/**
* Dispatch received a real-time update.
*
Expand Down Expand Up @@ -140,6 +149,8 @@ class EventDispatcher {
this.announce('messageAction', event.data);
else if (event.type === subscribe_1.PubNubEventType.Files)
this.announce('file', event.data);
else if (event.type === subscribe_1.PubNubEventType.DataSync)
this.announce('dataSync', event.data);
}
/**
* Dispatch received connection status change.
Expand Down
4 changes: 3 additions & 1 deletion lib/core/components/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ class AbstractRequest {
if (headers)
request.headers = headers;
// Attach body (if required).
if (request.method === transport_request_1.TransportMethod.POST || request.method === transport_request_1.TransportMethod.PATCH) {
if (request.method === transport_request_1.TransportMethod.POST ||
request.method === transport_request_1.TransportMethod.PATCH ||
request.method === transport_request_1.TransportMethod.PUT) {
const [body, formData] = [this.body, this.formData];
if (formData)
request.formData = formData;
Expand Down
25 changes: 19 additions & 6 deletions lib/core/components/retry-policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.RetryPolicy = exports.Endpoint = void 0;
const categories_1 = __importDefault(require("../constants/categories"));
/**
* HTTP status codes which represent definitive client-side outcomes and shouldn't be retried.
*
* @internal
*/
const NON_RETRIABLE_STATUS_CODES = [404, 409];
// --------------------------------------------------------
// ------------------------ Types -------------------------
// --------------------------------------------------------
Expand Down Expand Up @@ -122,7 +128,7 @@ var Endpoint;
class RetryPolicy {
static None() {
return {
shouldRetry(_request, _response, _errorCategory, _attempt) {
shouldRetry(_request, _response, _errorCategory, _attempt, _statusCode) {
return false;
},
getDelay(_attempt, _response) {
Expand All @@ -139,8 +145,8 @@ class RetryPolicy {
delay: configuration.delay,
maximumRetry: configuration.maximumRetry,
excluded: (_a = configuration.excluded) !== null && _a !== void 0 ? _a : [],
shouldRetry(request, response, error, attempt) {
return isRetriableRequest(request, response, error, attempt !== null && attempt !== void 0 ? attempt : 0, this.maximumRetry, this.excluded);
shouldRetry(request, response, error, attempt, statusCode) {
return isRetriableRequest(request, response, error, attempt !== null && attempt !== void 0 ? attempt : 0, this.maximumRetry, this.excluded, statusCode);
},
getDelay(_, response) {
let delay = -1;
Expand All @@ -163,8 +169,8 @@ class RetryPolicy {
maximumDelay: configuration.maximumDelay,
maximumRetry: configuration.maximumRetry,
excluded: (_a = configuration.excluded) !== null && _a !== void 0 ? _a : [],
shouldRetry(request, response, error, attempt) {
return isRetriableRequest(request, response, error, attempt !== null && attempt !== void 0 ? attempt : 0, this.maximumRetry, this.excluded);
shouldRetry(request, response, error, attempt, statusCode) {
return isRetriableRequest(request, response, error, attempt !== null && attempt !== void 0 ? attempt : 0, this.maximumRetry, this.excluded, statusCode);
},
getDelay(attempt, response) {
let delay = -1;
Expand All @@ -191,12 +197,15 @@ exports.RetryPolicy = RetryPolicy;
* @param retryAttempt - Current retry attempt.
* @param maximumRetry - Maximum retry attempts count according to the retry policy.
* @param excluded - List of endpoints for which retry policy won't be applied.
* @param statusCode - Response HTTP status code (available when request failed with an error and hence there is no
* `res`).
*
* @return `true` if request can be retried.
*
* @internal
*/
const isRetriableRequest = (req, res, errorCategory, retryAttempt, maximumRetry, excluded) => {
const isRetriableRequest = (req, res, errorCategory, retryAttempt, maximumRetry, excluded, statusCode) => {
var _a;
if (errorCategory) {
if (errorCategory === categories_1.default.PNCancelledCategory ||
errorCategory === categories_1.default.PNBadRequestCategory ||
Expand All @@ -207,6 +216,10 @@ const isRetriableRequest = (req, res, errorCategory, retryAttempt, maximumRetry,
return false;
else if (retryAttempt > maximumRetry)
return false;
// Status code is reported separately from the `res` when the request failed with an error.
const status = (_a = res === null || res === void 0 ? void 0 : res.status) !== null && _a !== void 0 ? _a : statusCode;
if (status !== undefined && NON_RETRIABLE_STATUS_CODES.includes(status))
return false;
return res ? res.status === 429 || res.status >= 500 : true;
};
/**
Expand Down
71 changes: 69 additions & 2 deletions lib/core/components/token_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,15 @@ class TokenManager {
* @returns Information about resources and permissions which has been granted for them.
*/
parseToken(tokenString) {
var _a, _b;
const parsed = this.cbor.decodeToken(tokenString);
if (parsed !== undefined) {
const uuidResourcePermissions = parsed.res.uuid ? Object.keys(parsed.res.uuid) : [];
const userResourcePermissions = parsed.res.usr ? Object.keys(parsed.res.usr) : [];
const channelResourcePermissions = Object.keys(parsed.res.chan);
const groupResourcePermissions = Object.keys(parsed.res.grp);
const uuidPatternPermissions = parsed.pat.uuid ? Object.keys(parsed.pat.uuid) : [];
const userPatternPermissions = parsed.pat.usr ? Object.keys(parsed.pat.usr) : [];
const channelPatternPermissions = Object.keys(parsed.pat.chan);
const groupPatternPermissions = Object.keys(parsed.pat.grp);
const result = {
Expand All @@ -63,14 +66,19 @@ class TokenManager {
signature: parsed.sig,
};
const uuidResources = uuidResourcePermissions.length > 0;
const userResources = userResourcePermissions.length > 0;
const channelResources = channelResourcePermissions.length > 0;
const groupResources = groupResourcePermissions.length > 0;
if (uuidResources || channelResources || groupResources) {
if (uuidResources || userResources || channelResources || groupResources) {
result.resources = {};
if (uuidResources) {
const uuids = (result.resources.uuids = {});
uuidResourcePermissions.forEach((id) => (uuids[id] = this.extractPermissions(parsed.res.uuid[id])));
}
if (userResources) {
const users = (result.resources.users = {});
userResourcePermissions.forEach((id) => (users[id] = this.extractCrudPermissions(parsed.res.usr[id])));
}
if (channelResources) {
const channels = (result.resources.channels = {});
channelResourcePermissions.forEach((id) => (channels[id] = this.extractPermissions(parsed.res.chan[id])));
Expand All @@ -80,15 +88,23 @@ class TokenManager {
groupResourcePermissions.forEach((id) => (groups[id] = this.extractPermissions(parsed.res.grp[id])));
}
}
const resourceDataSync = this.extractDataSyncScopes(parsed.res);
if (resourceDataSync)
((_a = result.resources) !== null && _a !== void 0 ? _a : (result.resources = {})).dataSync = resourceDataSync;
const uuidPatterns = uuidPatternPermissions.length > 0;
const userPatterns = userPatternPermissions.length > 0;
const channelPatterns = channelPatternPermissions.length > 0;
const groupPatterns = groupPatternPermissions.length > 0;
if (uuidPatterns || channelPatterns || groupPatterns) {
if (uuidPatterns || userPatterns || channelPatterns || groupPatterns) {
result.patterns = {};
if (uuidPatterns) {
const uuids = (result.patterns.uuids = {});
uuidPatternPermissions.forEach((id) => (uuids[id] = this.extractPermissions(parsed.pat.uuid[id])));
}
if (userPatterns) {
const users = (result.patterns.users = {});
userPatternPermissions.forEach((id) => (users[id] = this.extractCrudPermissions(parsed.pat.usr[id])));
}
if (channelPatterns) {
const channels = (result.patterns.channels = {});
channelPatternPermissions.forEach((id) => (channels[id] = this.extractPermissions(parsed.pat.chan[id])));
Expand All @@ -98,6 +114,9 @@ class TokenManager {
groupPatternPermissions.forEach((id) => (groups[id] = this.extractPermissions(parsed.pat.grp[id])));
}
}
const patternDataSync = this.extractDataSyncScopes(parsed.pat);
if (patternDataSync)
((_b = result.patterns) !== null && _b !== void 0 ? _b : (result.patterns = {})).dataSync = patternDataSync;
if (parsed.meta && Object.keys(parsed.meta).length > 0)
result.meta = parsed.meta;
return result;
Expand Down Expand Up @@ -137,5 +156,53 @@ class TokenManager {
permissionsResult.read = true;
return permissionsResult;
}
/**
* Extract DataSync permission scopes from a token permissions section.
*
* The `datasync:*` wire keys are only present for tokens which granted DataSync permissions, so a
* result is returned only when at least one scope carries permissions.
*
* @param section - Raw `res` or `pat` permissions section decoded from the token.
*
* @returns Human-readable DataSync permission scopes, or `undefined` when none are granted.
*/
extractDataSyncScopes(section) {
const dataSyncScopes = [
['entities', 'datasync:entities'],
['relationships', 'datasync:relationships'],
['memberships', 'datasync:memberships'],
];
let result;
dataSyncScopes.forEach(([scope, wireKey]) => {
const permissions = section[wireKey];
if (!permissions)
return;
const ids = Object.keys(permissions);
if (ids.length === 0)
return;
const scopeResult = ((result !== null && result !== void 0 ? result : (result = {}))[scope] = {});
ids.forEach((id) => (scopeResult[id] = this.extractCrudPermissions(permissions[id])));
});
return result;
}
/**
* Extract CRUD-only access permission information.
*
* Shared by the `usr` wire key (which backs the `users` grant scope) and the `datasync:*` wire
* keys — both carry the same CRUD bit layout and none of the `read` / `write` / `manage` / `join`
* bits decoded by {@link extractPermissions}.
*
* @param permissions - Bit-encoded resource permissions.
*
* @returns Human-readable CRUD resource permissions.
*/
extractCrudPermissions(permissions) {
return {
create: (permissions & 16) === 16,
get: (permissions & 32) === 32,
update: (permissions & 64) === 64,
delete: (permissions & 8) === 8,
};
}
}
exports.TokenManager = TokenManager;
Loading
Loading