arnacon-webrtc-service 0.1.4 → 0.1.6

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.
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "development": {
3
3
  "roflBaseUrl": "https://p8000.m1485.test-proxy-b.rofl.app",
4
+ "useLocalRoflLogic": true,
5
+ "roflLogic": {
6
+ "rpc": "https://testnet.sapphire.oasis.io",
7
+ "chainId": 23295,
8
+ "businessNumberDbAddress": "0x684F9797de793E603368A8Fe1Fe663aAeb327621",
9
+ "callerIdPoolAddress": "0xFB3DD146F1aFfbA661533Ec49298D01f20104CF6"
10
+ },
4
11
  "messageProcessorUrl": "https://europe-west3-asterisk-tts-test.cloudfunctions.net/client_msg_processor",
5
12
  "polygon": {
6
13
  "rpc": "https://polygon-bor-rpc.publicnode.com",
@@ -65,6 +72,13 @@
65
72
  },
66
73
  "production": {
67
74
  "roflBaseUrl": "https://p8000.m206.opf-mainnet-rofl-35.rofl.app",
75
+ "useLocalRoflLogic": true,
76
+ "roflLogic": {
77
+ "rpc": "https://sapphire.oasis.io",
78
+ "chainId": 23294,
79
+ "businessNumberDbAddress": "",
80
+ "callerIdPoolAddress": "0x903796dc34Bb9A5eD76A41bBA6C40CfebD1f4269"
81
+ },
68
82
  "messageProcessorUrl": "https://europe-west3-asterisk-tts-test.cloudfunctions.net/client_msg_processor",
69
83
  "polygon": {
70
84
  "rpc": "https://polygon-bor-rpc.publicnode.com",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arnacon-webrtc-service",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Arnacon WebRTC core runtime and service modules",
5
5
  "main": "./webRTCservice/core.js",
6
6
  "type": "commonjs",
@@ -15,6 +15,23 @@ function createBlockchainApi({
15
15
  const SAPPHIRE_RPC = config.sapphire.rpc;
16
16
  const SAPPHIRE_TESTNET_RPC = config.sapphireTestnet.rpc;
17
17
  const NFT_CALLER_ID_POOL_ADDRESS = config.sapphireTestnet.NFTCallerIdPool;
18
+ const ROFL_LOGIC_CONFIG = config.roflLogic || {};
19
+ const ROFL_LOGIC_RPC =
20
+ process.env.ROFL_LOGIC_RPC_URL ||
21
+ ROFL_LOGIC_CONFIG.rpc ||
22
+ SAPPHIRE_TESTNET_RPC ||
23
+ SAPPHIRE_RPC;
24
+ const ROFL_LOGIC_CHAIN_ID =
25
+ Number(process.env.ROFL_LOGIC_CHAIN_ID || ROFL_LOGIC_CONFIG.chainId || 0) || undefined;
26
+ const ROFL_BUSINESS_NUMBER_DB_ADDRESS =
27
+ process.env.ROFL_LOGIC_BUSINESS_NUMBER_DB_ADDRESS ||
28
+ ROFL_LOGIC_CONFIG.businessNumberDbAddress ||
29
+ "";
30
+ const ROFL_CALLER_ID_POOL_ADDRESS =
31
+ process.env.ROFL_LOGIC_CALLER_ID_POOL_ADDRESS ||
32
+ ROFL_LOGIC_CONFIG.callerIdPoolAddress ||
33
+ NFT_CALLER_ID_POOL_ADDRESS;
34
+ const ROFL_PKEY = process.env.ROFL_LOGIC_PKEY || process.env.PKEY || "";
18
35
 
19
36
  const ENS_REGISTRY_ABI = [
20
37
  "function owner(bytes32 node) view returns (address)",
@@ -36,10 +53,27 @@ function createBlockchainApi({
36
53
  "function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)",
37
54
  "function getCallerIdByTokenId(uint256 tokenId) view returns (string phoneNumber, string metadata, address owner)",
38
55
  ];
56
+ const BUSINESS_NUMBER_DB_ABI = [
57
+ "function getPhoneNumber(string identifier) view returns (string)",
58
+ ];
59
+ const CALLER_ID_POOL_ROFL_ABI = [
60
+ "function findNextOwnedCallerId(uint256 startIndex, address expectedOwner, uint256 maxAttempts) view returns (string phoneNumber, uint256 foundIndex, bool found)",
61
+ "function getPoolSize() view returns (uint256)",
62
+ "function admin() view returns (address)",
63
+ "function getTokens() view returns (uint256[])",
64
+ "function getCallerIdByTokenId(uint256 tokenId) view returns (string phoneNumber, string metadata, address owner)",
65
+ ];
39
66
 
40
67
  let polygonProvider = null;
41
68
  let sapphireProvider = null;
42
69
  let sapphireTestnetProvider = null;
70
+ let roflLogicProvider = null;
71
+ let businessNumberDbContract = null;
72
+ let callerIdPoolRoflContract = null;
73
+ let roflPoolOwnerAddress = null;
74
+ let roflCallerIdIndex = 0;
75
+ let roflAddress = null;
76
+ let roflOwnerResolved = false;
43
77
 
44
78
  function getPolygonProvider() {
45
79
  if (!polygonProvider) polygonProvider = new ethers.providers.JsonRpcProvider(POLYGON_RPC);
@@ -56,6 +90,77 @@ function createBlockchainApi({
56
90
  return sapphireTestnetProvider;
57
91
  }
58
92
 
93
+ function getRoflLogicProvider() {
94
+ if (!roflLogicProvider) {
95
+ if (ROFL_LOGIC_CHAIN_ID) {
96
+ roflLogicProvider = new ethers.providers.JsonRpcProvider(ROFL_LOGIC_RPC, ROFL_LOGIC_CHAIN_ID);
97
+ } else {
98
+ roflLogicProvider = new ethers.providers.JsonRpcProvider(ROFL_LOGIC_RPC);
99
+ }
100
+ }
101
+ return roflLogicProvider;
102
+ }
103
+
104
+ function normalizeContractAddress(value) {
105
+ const normalized = String(value || "").trim();
106
+ if (!normalized || normalized === "0x0000000000000000000000000000000000000000") return "";
107
+ return normalized;
108
+ }
109
+
110
+ function toSafeNumber(value, fallback = 0) {
111
+ if (typeof value === "number") return value;
112
+ if (typeof value === "bigint") return Number(value);
113
+ if (value && typeof value.toString === "function") return Number(value.toString());
114
+ return fallback;
115
+ }
116
+
117
+ function getRoflAddress() {
118
+ if (!roflAddress && ROFL_PKEY) {
119
+ try {
120
+ roflAddress = new ethers.Wallet(ROFL_PKEY).address;
121
+ } catch (err) {
122
+ logger.error(`[ROFL_LOCAL] invalid PKEY: ${err.message}`);
123
+ }
124
+ }
125
+ return roflAddress;
126
+ }
127
+
128
+ function getBusinessNumberDbContract() {
129
+ if (!businessNumberDbContract) {
130
+ const address = normalizeContractAddress(ROFL_BUSINESS_NUMBER_DB_ADDRESS);
131
+ if (!address) return null;
132
+ businessNumberDbContract = new ethers.Contract(address, BUSINESS_NUMBER_DB_ABI, getRoflLogicProvider());
133
+ }
134
+ return businessNumberDbContract;
135
+ }
136
+
137
+ function getRoflCallerIdPoolContract() {
138
+ if (!callerIdPoolRoflContract) {
139
+ const address = normalizeContractAddress(ROFL_CALLER_ID_POOL_ADDRESS);
140
+ if (!address) return null;
141
+ callerIdPoolRoflContract = new ethers.Contract(address, CALLER_ID_POOL_ROFL_ABI, getRoflLogicProvider());
142
+ }
143
+ return callerIdPoolRoflContract;
144
+ }
145
+
146
+ async function getRoflPoolOwnerAddress() {
147
+ if (roflOwnerResolved) return roflPoolOwnerAddress || getRoflAddress();
148
+
149
+ roflOwnerResolved = true;
150
+ const pool = getRoflCallerIdPoolContract();
151
+ if (!pool) {
152
+ roflPoolOwnerAddress = getRoflAddress();
153
+ return roflPoolOwnerAddress;
154
+ }
155
+ try {
156
+ roflPoolOwnerAddress = await pool.admin();
157
+ } catch (err) {
158
+ logger.warn(`[ROFL_LOCAL] pool admin() failed, falling back to ROFL key address: ${err.message}`);
159
+ roflPoolOwnerAddress = getRoflAddress();
160
+ }
161
+ return roflPoolOwnerAddress;
162
+ }
163
+
59
164
  function isEthAddress(str) {
60
165
  return /^0x[0-9a-fA-F]{40}$/.test(str);
61
166
  }
@@ -285,6 +390,74 @@ function createBlockchainApi({
285
390
  }
286
391
  }
287
392
 
393
+ async function roflFindBusinessNumber(callee) {
394
+ const contract = getBusinessNumberDbContract();
395
+ if (!contract) return null;
396
+
397
+ const businessName = String(callee || "").trim().toLowerCase();
398
+ if (!businessName) return null;
399
+ try {
400
+ const phoneNumber = await contract.getPhoneNumber(businessName);
401
+ return phoneNumber && phoneNumber !== "" ? phoneNumber : null;
402
+ } catch (err) {
403
+ logger.error(`[ROFL_LOCAL] find-business-number failed for ${businessName}: ${err.message}`);
404
+ return null;
405
+ }
406
+ }
407
+
408
+ async function roflAssignFromNumber() {
409
+ const pool = getRoflCallerIdPoolContract();
410
+ if (!pool) return null;
411
+
412
+ try {
413
+ const poolSize = await pool.getPoolSize();
414
+ if (toSafeNumber(poolSize) <= 0) return null;
415
+
416
+ const roflKeyAddress = getRoflAddress();
417
+
418
+ // Explicit request: if PKEY is not set, assign directly from pool
419
+ // without owner filtering (pure round-robin by token list index).
420
+ if (!roflKeyAddress) {
421
+ const tokens = await pool.getTokens();
422
+ if (!tokens || tokens.length === 0) return null;
423
+
424
+ const idx = roflCallerIdIndex % tokens.length;
425
+ const tokenId = tokens[idx];
426
+ const [fromNumber] = await pool.getCallerIdByTokenId(tokenId);
427
+ if (!fromNumber || fromNumber === "") return null;
428
+
429
+ roflCallerIdIndex = idx + 1;
430
+ return fromNumber;
431
+ }
432
+
433
+ const ownerForPool = (await getRoflPoolOwnerAddress()) || roflKeyAddress;
434
+ if (!ownerForPool) return null;
435
+
436
+ const [fromNumber, foundIndex, found] = await pool.findNextOwnedCallerId(
437
+ ethers.BigNumber.from(roflCallerIdIndex),
438
+ ownerForPool,
439
+ poolSize,
440
+ );
441
+
442
+ if (!found || !fromNumber || fromNumber === "") return null;
443
+ roflCallerIdIndex = toSafeNumber(foundIndex, roflCallerIdIndex) + 1;
444
+ return fromNumber;
445
+ } catch (err) {
446
+ logger.error(`[ROFL_LOCAL] assign-from-number failed: ${err.message}`);
447
+ return null;
448
+ }
449
+ }
450
+
451
+ function getRoflLogicInfo() {
452
+ return {
453
+ rpc: ROFL_LOGIC_RPC || null,
454
+ chainId: ROFL_LOGIC_CHAIN_ID || null,
455
+ businessNumberDbAddress: normalizeContractAddress(ROFL_BUSINESS_NUMBER_DB_ADDRESS) || null,
456
+ callerIdPoolAddress: normalizeContractAddress(ROFL_CALLER_ID_POOL_ADDRESS) || null,
457
+ roflAddress: getRoflAddress() || null,
458
+ };
459
+ }
460
+
288
461
  return {
289
462
  ethers,
290
463
  getPolygonProvider,
@@ -302,6 +475,9 @@ function createBlockchainApi({
302
475
  verifyHttpSignalingSignature,
303
476
  resolveCallerServiceProviderContract,
304
477
  nftGetOwnedNumber,
478
+ roflFindBusinessNumber,
479
+ roflAssignFromNumber,
480
+ getRoflLogicInfo,
305
481
  getRpcForNetwork,
306
482
  };
307
483
  }
@@ -6,6 +6,9 @@ function createCallRouter({
6
6
  roflBaseUrl,
7
7
  fetchImpl = fetch,
8
8
  logger = console,
9
+ useLocalRoflLogic = false,
10
+ lookupBusinessNumberImpl = null,
11
+ assignFromNumberImpl = null,
9
12
  }) {
10
13
  function isRawEmail(str) {
11
14
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str) && !str.endsWith(".global");
@@ -34,6 +37,15 @@ function createCallRouter({
34
37
  }
35
38
 
36
39
  async function roflFindBusinessNumber(callee) {
40
+ if (useLocalRoflLogic && typeof lookupBusinessNumberImpl === "function") {
41
+ try {
42
+ return (await lookupBusinessNumberImpl(callee)) || null;
43
+ } catch (err) {
44
+ logger.error(`[ROFL_LOCAL] find-business-number failed for ${callee}: ${err.message}`);
45
+ return null;
46
+ }
47
+ }
48
+
37
49
  try {
38
50
  const resp = await fetchImpl(`${roflBaseUrl}/find-business-number`, {
39
51
  method: "POST",
@@ -82,6 +94,15 @@ function createCallRouter({
82
94
  }
83
95
 
84
96
  async function roflAssignFromNumber() {
97
+ if (useLocalRoflLogic && typeof assignFromNumberImpl === "function") {
98
+ try {
99
+ return (await assignFromNumberImpl()) || null;
100
+ } catch (err) {
101
+ logger.error(`[ROFL_LOCAL] assign-from-number failed: ${err.message}`);
102
+ return null;
103
+ }
104
+ }
105
+
85
106
  try {
86
107
  const resp = await fetchImpl(`${roflBaseUrl}/assign-from-number`);
87
108
  if (!resp.ok) return null;
@@ -237,6 +237,7 @@ const config = {
237
237
  polygon: pickRuntimeConfig("polygon", {}),
238
238
  sapphire: pickRuntimeConfig("sapphire", {}),
239
239
  sapphireTestnet: pickRuntimeConfig("sapphireTestnet", {}),
240
+ roflLogic: pickRuntimeConfig("roflLogic", {}),
240
241
  };
241
242
  const serviceRuntimes = loadedServices;
242
243
  const selectedServiceId = process.env.SERVICE_ID || null;
@@ -268,6 +269,8 @@ const INTERNAL_BIND_IP = config.bindIp || "127.0.0.1";
268
269
 
269
270
  // ROFL API config
270
271
  const ROFL_BASE_URL = config.roflBaseUrl;
272
+ const USE_LOCAL_ROFL_LOGIC =
273
+ String(process.env.USE_LOCAL_ROFL_LOGIC || pickRuntimeConfig("useLocalRoflLogic", true)) === "true";
271
274
  const MESSAGE_PROCESSOR_URL =
272
275
  config.messageProcessorUrl ||
273
276
  "https://europe-west3-asterisk-tts-test.cloudfunctions.net/client_msg_processor";
@@ -313,7 +316,17 @@ const callRouterApi = createCallRouter({
313
316
  roflBaseUrl: ROFL_BASE_URL,
314
317
  fetchImpl: fetch,
315
318
  logger: console,
319
+ useLocalRoflLogic: USE_LOCAL_ROFL_LOGIC,
320
+ lookupBusinessNumberImpl: (...args) => blockchainApi.roflFindBusinessNumber(...args),
321
+ assignFromNumberImpl: (...args) => blockchainApi.roflAssignFromNumber(...args),
316
322
  });
323
+ const roflLogicInfo = blockchainApi.getRoflLogicInfo();
324
+ console.log(
325
+ `[ROFL] mode=${USE_LOCAL_ROFL_LOGIC ? "local_rofl_logic" : "remote_http"} ` +
326
+ `baseUrl=${ROFL_BASE_URL || "n/a"} rpc=${roflLogicInfo.rpc || "n/a"} ` +
327
+ `chainId=${roflLogicInfo.chainId || "n/a"} businessDb=${roflLogicInfo.businessNumberDbAddress || "n/a"} ` +
328
+ `callerIdPool=${roflLogicInfo.callerIdPoolAddress || "n/a"} roflAddress=${roflLogicInfo.roflAddress || "n/a"}`,
329
+ );
317
330
  const notificationApi = createNotificationApi({
318
331
  blockchainApi,
319
332
  signalingPlanAbi: SIGNALING_PLAN_ABI,