feat: Add presence heartbeat for Matrix online status

- matrix-gateway: POST /internal/matrix/presence/online endpoint
- usePresenceHeartbeat hook with activity tracking
- Auto away after 5 min inactivity
- Offline on page close/visibility change
- Integrated in MatrixChatRoom component
This commit is contained in:
Apple
2025-11-27 00:19:40 -08:00
parent 5bed515852
commit 3de3c8cb36
6371 changed files with 1317450 additions and 932 deletions

View File

@@ -0,0 +1,100 @@
/**
* PDP Service (MVP)
* Based on: pdp_access.md, core-services-mvp.md
*
* Responsibilities:
* - Centralized access decision making
* - Interpret policies from pdp_access.md
* - Provide simple API for other services
*/
import type { PdpRequest, PdpResponse, PolicyId, PdpContext } from '../../domain/pdp/policy.model';
import { policiesConfig } from './policies.config';
import { walletService } from '../wallet/wallet.service';
export class PdpService {
/**
* Check policy and return decision
*/
async check(
policyId: PolicyId,
resource: Record<string, unknown>,
context: PdpContext
): Promise<PdpResponse> {
const policy = policiesConfig[policyId];
if (!policy) {
return {
decision: 'deny',
reason: `Policy ${policyId} not found`,
};
}
// Evaluate policy conditions
const result = await this.evaluatePolicy(policy, resource, context);
return result;
}
private async evaluatePolicy(
policy: any,
resource: Record<string, unknown>,
context: PdpContext
): Promise<PdpResponse> {
// MVP: Simple evaluation
// Future: More complex condition evaluation
// Example: policy.dao.create
if (policy.id === 'policy.dao.create') {
const hasEnough = await walletService.hasEnoughForDaoCreate(context.userId || '');
if (!hasEnough) {
return {
decision: 'deny',
reason: 'INSUFFICIENT_BALANCE',
details: {
required: { DAAR: 1.0, DAARION: 0.01 },
},
};
}
return { decision: 'allow' };
}
// Example: policy.platform.create
if (policy.id === 'policy.platform.create') {
const hasEnough = await walletService.hasEnoughForPlatformCreate(context.userId || '');
if (!hasEnough) {
return {
decision: 'deny',
reason: 'INSUFFICIENT_BALANCE',
details: {
required: { DAARION: 1.0 },
},
};
}
return { decision: 'allow' };
}
// Example: policy.vendor.register
if (policy.id === 'policy.vendor.register') {
const hasEnough = await walletService.hasEnoughForVendorRegister(context.userId || '');
if (!hasEnough) {
return {
decision: 'deny',
reason: 'INSUFFICIENT_STAKED_DAARION',
details: {
required: { DAARION: 0.01 },
},
};
}
return { decision: 'allow' };
}
// Default: allow (for MVP, can be more restrictive later)
return { decision: 'allow' };
}
}
// Singleton instance
export const pdpService = new PdpService();

View File

@@ -0,0 +1,76 @@
/**
* Policies Configuration
* Based on: pdp_access.md
*
* Initial set of policies for MVP
*/
export const policiesConfig = {
'policy.dao.create': {
id: 'policy.dao.create',
description: 'Створення нового MicroDAO',
conditions: [
{
type: 'or',
rules: [
{ type: 'balance', token: 'DAAR', gte: 1 },
{ type: 'balance', token: 'DAARION', gte: 0.01 },
],
},
],
},
'policy.vendor.register': {
id: 'policy.vendor.register',
description: 'Реєстрація вендора на платформі',
conditions: [
{ type: 'staked', token: 'DAARION', gte: 0.01 },
],
},
'policy.platform.create': {
id: 'policy.platform.create',
description: 'Створення платформи',
conditions: [
{ type: 'staked', token: 'DAARION', gte: 1 },
],
},
'policy.federation.join': {
id: 'policy.federation.join',
description: 'Вступ DAO до SuperDAO',
conditions: [
{ type: 'role', value: 'owner' },
{ type: 'target', property: 'federation_mode', value: 'superdao' },
],
},
'policy.federation.leave': {
id: 'policy.federation.leave',
description: 'Вихід DAO з SuperDAO',
conditions: [
{ type: 'role', value: 'owner' },
],
},
'policy.federation.create-superdao': {
id: 'policy.federation.create-superdao',
description: 'Створення SuperDAO',
conditions: [
{ type: 'role', value: 'owner' },
{ type: 'dao', property: 'child_count', gte: 1 },
],
},
'policy.federation.dissolve': {
id: 'policy.federation.dissolve',
description: 'Розформування федерації',
conditions: [
{ type: 'role', value: 'owner' },
{ type: 'dao', property: 'level', ne: 'A1' },
],
},
'policy.agent.run': {
id: 'policy.agent.run',
description: 'Запуск агента',
conditions: [
{ type: 'agent', property: 'registered', value: true },
],
},
} as const;