Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
2 changes: 1 addition & 1 deletion .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: unreal-engine
schema: 1
version: 2.0.5
version: 2.1.0
scm: github.com/pubnub/unreal-engine
changelog:
- date: 2026-05-21
Expand Down
2 changes: 1 addition & 1 deletion PubnubLibrary.uplugin
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"FileVersion": 3,
"Version": 20,
"Version": 21,
"VersionName": "2.0.5",
"FriendlyName": "Pubnub Unreal SDK",
"Description": "Quickly add interactive features to your game that scale without building your backend infrastructure.",
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,38 @@ Each PubNub API is available through Blueprints and C++ code. For further inform

You can copy the Blueprints from the docs and paste them into your Unreal Editor.

## Security

### Keys and credentials

PubNub provides three keys per keyset:

| Key | Safe for client builds | Purpose |
| --- | --- | --- |
| Publish Key | Yes | Publish messages |
| Subscribe Key | Yes | Subscribe to channels |
| Secret Key | **No** | Root/admin access to the entire keyset |

The **Secret Key grants unrestricted permissions** on your keyset — publish on any channel, read history, mint or revoke PAM tokens, and modify App Context data. **Never ship a Secret Key in a game client build.**

Global plugin settings (`Project Settings → Pubnub SDK`) store only the Publish Key and Subscribe Key. These are safe to include in `DefaultEngine.ini` and packaged client builds.

### Recommended architecture

**Game clients (shipped builds):**

1. Configure only Publish Key and Subscribe Key (via plugin settings or `FPubnubConfig`).
2. Obtain a scoped PAM v3 access token from your backend.
3. Call `SetAuthToken` on the client before subscribing or publishing.

**Dedicated servers / backend tooling:**

1. Provide the Secret Key in `FPubnubConfig` when calling `CreatePubnubClient` — not in global plugin settings.
2. Load the Secret Key from a secure source (environment variable, server-only config, secrets manager).
3. Use `GrantToken` / `RevokeToken` / `SetSecretKey` only in server-side code.

For more details, see the [PubNub Access Manager documentation](https://www.pubnub.com/docs/general/setup/access-manager).

## Support

If you **need help** or have a **general question**, contact [support@pubnub.com](mailto:support@pubnub.com).
Expand Down
46 changes: 42 additions & 4 deletions Scripts/merge_into_fab_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@
- LICENSE (from Pubnub, or PubnubChat if Pubnub has none)

Run from project root or any directory; paths are resolved from script location.
Prompts for VersionName; the merged PubnubLibrary.uplugin uses FriendlyName "PubNub Gaming SDK".
Prompts for VersionName when omitted; the merged PubnubLibrary.uplugin uses FriendlyName "PubNub Gaming SDK".

CLI examples:
python merge_into_fab_sdk.py --version-name 1.0.2
python merge_into_fab_sdk.py -v 1.0.2
"""

from __future__ import annotations

import argparse
import json
import shutil
import sys
from pathlib import Path


Expand Down Expand Up @@ -63,7 +69,35 @@ def _ignore_source_dirs(_dir: Path, names: list[str]) -> list[str]:
return ignored


def main() -> None:
def _prompt(label: str, *, required: bool = True) -> str:
while True:
value = input(f"{label}: ").strip()
if value or not required:
return value
print(" Value is required.")


def _resolve_input(label: str, arg_value: str | None, *, required: bool = True) -> str:
if arg_value is not None:
return arg_value.strip()
return _prompt(label, required=required)


def _build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Merge Pubnub and PubnubChat plugins into a single FAB-ready plugin.",
)
parser.add_argument(
"-v",
"--version-name",
help="Package VersionName (e.g. 1.0.2). Prompts when omitted.",
)
return parser


def main(argv: list[str] | None = None) -> None:
args = _build_arg_parser().parse_args(argv)

script_dir = Path(__file__).resolve().parent
# Project root: Scripts -> Pubnub -> Plugins -> project root
project_root = script_dir.parent.parent.parent
Expand All @@ -83,7 +117,7 @@ def main() -> None:
if not pubnub_chat_uplugin.is_file():
raise SystemExit(f"Missing {pubnub_chat_uplugin}")

version_name = input("Enter package VersionName (e.g. 1.0.2): ").strip()
version_name = _resolve_input("Enter package VersionName (e.g. 1.0.2)", args.version_name)
if not version_name:
raise SystemExit("VersionName is required (non-empty).")

Expand Down Expand Up @@ -151,4 +185,8 @@ def main() -> None:


if __name__ == "__main__":
main()
try:
main()
except KeyboardInterrupt:
print("\nAborted.", file=sys.stderr)
sys.exit(130)
218 changes: 218 additions & 0 deletions Scripts/prepare_for_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#!/usr/bin/env python3
"""
Prepare the Pubnub Unreal SDK for release.

Updates:
- PubnubLibrary.uplugin: increments Version (VersionName is unchanged)
- PubnubLibraryVersion.h: sets PUBNUB_LIBRARY_VERSION_{MAJOR,MINOR,PATCH}
and optionally PUBNUB_C_CORE_VERSION
- .pubnub.yml: sets top-level version (changelog entries are unchanged)

Run from any directory; paths are resolved from script location.

CLI examples:
python prepare_for_release.py --release-version 2.0.6
python prepare_for_release.py -r 2.0.6 --c-core-version 7.2.4
python prepare_for_release.py -r 2.0.6 --c-core-version
"""

from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path


_VERSION_PATTERN = re.compile(r"^(\d+)\.(\d+)\.(\d+)$")


def _parse_semver(label: str, value: str) -> tuple[int, int, int]:
match = _VERSION_PATTERN.match(value.strip())
if not match:
raise ValueError(f"{label} must be in MAJOR.MINOR.PATCH format (e.g. 2.0.5), got: {value!r}")
return int(match.group(1)), int(match.group(2)), int(match.group(3))


def _prompt(label: str, *, required: bool = True) -> str:
while True:
value = input(f"{label}: ").strip()
if value or not required:
return value
print(" Value is required.")


def _resolve_input(label: str, arg_value: str | None, *, required: bool = True) -> str:
if arg_value is not None:
return arg_value.strip()
return _prompt(label, required=required)


def _build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Prepare the Pubnub Unreal SDK for release.")
parser.add_argument(
"-r",
"--release-version",
help="Release version in MAJOR.MINOR.PATCH format. Prompts when omitted.",
)
parser.add_argument(
"-c",
"--c-core-version",
nargs="?",
const="",
default=None,
help=(
"C-Core version in MAJOR.MINOR.PATCH format. "
"Omit the flag to prompt; pass the flag alone to skip updating."
),
)
return parser


def _update_uplugin_version(uplugin_path: Path) -> int:
with uplugin_path.open("r", encoding="utf-8") as f:
uplugin = json.load(f)

old_version = uplugin.get("Version")
if not isinstance(old_version, int):
raise SystemExit(f"Unexpected Version field in {uplugin_path}: {old_version!r}")

new_version = old_version + 1
uplugin["Version"] = new_version

with uplugin_path.open("w", encoding="utf-8", newline="\n") as f:
json.dump(uplugin, f, indent="\t")
f.write("\n")

return new_version


def _update_version_header(
header_path: Path,
major: int,
minor: int,
patch: int,
c_core_version: str | None,
) -> None:
content = header_path.read_text(encoding="utf-8")

content, count = re.subn(
r"(#define PUBNUB_LIBRARY_VERSION_MAJOR )\d+",
rf"\g<1>{major}",
content,
count=1,
)
if count != 1:
raise SystemExit(f"Could not update PUBNUB_LIBRARY_VERSION_MAJOR in {header_path}")

content, count = re.subn(
r"(#define PUBNUB_LIBRARY_VERSION_MINOR )\d+",
rf"\g<1>{minor}",
content,
count=1,
)
if count != 1:
raise SystemExit(f"Could not update PUBNUB_LIBRARY_VERSION_MINOR in {header_path}")

content, count = re.subn(
r"(#define PUBNUB_LIBRARY_VERSION_PATCH )\d+",
rf"\g<1>{patch}",
content,
count=1,
)
if count != 1:
raise SystemExit(f"Could not update PUBNUB_LIBRARY_VERSION_PATCH in {header_path}")

if c_core_version is not None:
content, count = re.subn(
r'(#define PUBNUB_C_CORE_VERSION ")[^"]*(")',
rf"\g<1>{c_core_version}\g<2>",
content,
count=1,
)
if count != 1:
raise SystemExit(f"Could not update PUBNUB_C_CORE_VERSION in {header_path}")

header_path.write_text(content, encoding="utf-8", newline="\n")


def _update_pubnub_yml_version(pubnub_yml_path: Path, release_version: str) -> None:
content = pubnub_yml_path.read_text(encoding="utf-8")

# Top-level key is unindented; changelog entries use " version:".
content, count = re.subn(
r"^version: .+$",
f"version: {release_version}",
content,
count=1,
flags=re.MULTILINE,
)
if count != 1:
raise SystemExit(f"Could not update top-level version in {pubnub_yml_path}")

pubnub_yml_path.write_text(content, encoding="utf-8", newline="\n")


def main(argv: list[str] | None = None) -> None:
args = _build_arg_parser().parse_args(argv)

script_dir = Path(__file__).resolve().parent
plugin_root = script_dir.parent
uplugin_path = plugin_root / "PubnubLibrary.uplugin"
header_path = plugin_root / "Source" / "PubnubLibrary" / "Public" / "PubnubLibraryVersion.h"
pubnub_yml_path = plugin_root / ".pubnub.yml"

if not uplugin_path.is_file():
raise SystemExit(f"Plugin descriptor not found: {uplugin_path}")
if not header_path.is_file():
raise SystemExit(f"Version header not found: {header_path}")
if not pubnub_yml_path.is_file():
raise SystemExit(f"PubNub manifest not found: {pubnub_yml_path}")

print("Prepare Pubnub Unreal SDK for release\n")

release_version = _resolve_input("Release version (MAJOR.MINOR.PATCH)", args.release_version)
try:
major, minor, patch = _parse_semver("Release version", release_version)
except ValueError as exc:
raise SystemExit(str(exc)) from exc

c_core_input = _resolve_input(
"C-Core version (MAJOR.MINOR.PATCH, leave empty to skip)",
args.c_core_version,
required=False,
)
c_core_version: str | None = None
if c_core_input:
try:
c_major, c_minor, c_patch = _parse_semver("C-Core version", c_core_input)
except ValueError as exc:
raise SystemExit(str(exc)) from exc
c_core_version = f"{c_major}.{c_minor}.{c_patch}"

release_version_str = f"{major}.{minor}.{patch}"

new_uplugin_version = _update_uplugin_version(uplugin_path)
_update_version_header(header_path, major, minor, patch, c_core_version)
_update_pubnub_yml_version(pubnub_yml_path, release_version_str)

print("\nUpdated files:")
print(f" {uplugin_path}")
print(f" Version -> {new_uplugin_version} (VersionName unchanged)")
print(f" {pubnub_yml_path}")
print(f" version -> {release_version_str} (changelog unchanged)")
print(f" {header_path}")
print(f" PUBNUB_LIBRARY_VERSION -> {release_version_str}")
if c_core_version is not None:
print(f" PUBNUB_C_CORE_VERSION -> {c_core_version}")
else:
print(" PUBNUB_C_CORE_VERSION -> unchanged")


if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nAborted.", file=sys.stderr)
sys.exit(130)
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ FString UPubnubLogUtilities::LogToString(UObject* Value)
return LogToString(static_cast<const UObject*>(Value));
}

FString UPubnubLogUtilities::LogConfigToString(const FPubnubConfig& Config)
{
FPubnubConfig ConfigToLog = Config;
ConfigToLog.SecretKey = MaskSecretKeyForLog(Config.SecretKey);
return LogToString(ConfigToLog);
}

FString UPubnubLogUtilities::MaskSecretKeyForLog(const FString& SecretKey)
{
if (SecretKey.IsEmpty())
{
return SecretKey;
}

const int32 Length = SecretKey.Len();
const FString Prefix = SecretKey.Left(FMath::Min(8, Length));
return FString::Printf(TEXT("%s… (len=%d)"), *Prefix, Length);
}

FString UPubnubLogUtilities::LogUStructToString(const UScriptStruct* Struct, const void* StructData)
{
if (!Struct || !StructData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ FPubnubConfig UPubnubUtilities::PubnubConfigFromPluginSettings(UPubnubSettings*
FPubnubConfig Config;
Config.PublishKey = PubnubSettings->PublishKey;
Config.SubscribeKey = PubnubSettings->SubscribeKey;
Config.SecretKey = PubnubSettings->SecretKey;
Config.SetSecretKeyAutomatically = PubnubSettings->SetSecretKeyAutomatically;

return Config;
}
Expand Down
Loading
Loading