Documentation index for AI agents

GS2-Version

버전 체크 기능

애플리케이션 버전이나 추가 에셋의 버전, 이용약관에 동의한 버전 등을 판정하는 기능을 제공합니다.

버전 체크를 통과했을 때, 새로운 임시 GS2 클라이언트ID/시크릿을 발급할 수 있습니다.

이 기능을 이용함으로써, 앱에 내장된 GS2의 클라이언트ID/시크릿은 로그인 및 버전 체크를 수행하는 API만 호출할 수 있는 권한만 가지도록 하고, 버전 체크를 통과한 후에 실제로 게임을 플레이하기에 충분한 권한을 가진 클라이언트ID/시크릿을 받을 수 있도록 할 수 있습니다.

graph TD
  Boot["앱 실행"] --> Login["GS2-Account 로 로그인"]
  Login -- "최소 권한의<br/>클라이언트ID/시크릿" --> Check["GS2-Version<br/>CheckVersion"]
  Check -- "OK" --> Token["새로운 ProjectToken 취득<br/>(원래 권한)"]
  Check -- "Warning" --> Notify["플레이어에게 업데이트 안내"]
  Check -- "Error" --> Force["강제 버전 업"]
  Notify --> Token
  Token --> Game["게임 본편"]

버전 모델

네임스페이스에는 최대 10개의 버전 모델을 선언할 수 있습니다. 버전 체크에는 여러 항목을 설정할 수 있으며, 모든 버전 체크를 통과한 경우에만 체크를 통과할 수 있습니다.

버전 모델에는 두 종류가 있으며, “애플리케이션이 전송한 버전을 기반으로 버전을 판정"하거나 “로그인 중인 사용자가 과거에 동의한 약관의 버전을 기반으로 버전을 판정"할 수 있습니다.
전자를 “패시브 버전 체크”, 후자를 “액티브 버전 체크"라고 부릅니다.

마스터 항목설명
name버전 모델명(체크 시 식별자)
scopepassive(패시브) / active(액티브)
typesimple(단일 임계값) / schedule(시각에 따라 임계값 전환)
currentVersion액티브 버전 체크에서 최초 참여 시 자동으로 승인된 것으로 처리하는 버전
warningVersion경고를 표시하는 버전 임계값(이 버전 이하에서 warnings를 반환)
errorVersion오류로 처리하는 버전 임계값(이 버전 이하에서 errors를 반환)
scheduleVersionstype: schedule일 때 시각별 임계값을 정의
needSignature버전 정보에 서명을 요구할지 여부
signatureKeyId서명 검증에 사용하는 GS2-Key의 키ID
approveRequirement액티브 버전 체크 시 승인 요구 사항(required / optional)

버전 번호 형식

{major}.{minor}.{micro}
형식의 버전 번호를 이용할 수 있으며, 각 항목에는 정수값을 지정할 수 있습니다. 버전 값의 비교는 majorminormicro 순으로 이루어지며, 사전순이 아니라 숫자로 비교됩니다.

패시브 버전 체크

게임 실행 바이너리나, 게임이 다운로드한 에셋별 버전 체크에 이용합니다.

마스터 데이터에서는 버전 모델별로 “경고를 발생시키는 버전 임계값”, “오류로 처리하는 버전 임계값"을 설정할 수 있습니다.
클라이언트는 CheckVersion API에 현재 앱의 빌드 번호나 다운로드 완료된 에셋의 버전을 전송하고, 서버 측에서 임계값을 판정한 결과를 받습니다.

판정 결과는 다음 중 하나가 됩니다.

결과조건예상되는 동작
통과모든 항목이 warningVersion을 상회게임 본편으로 진행
경고 (warnings)어느 하나가 warningVersion 이하이면서 errorVersion 초과플레이어에게 업데이트를 안내하면서 계속 진행 가능
오류 (errors)어느 하나가 errorVersion 이하강제 버전 업

액티브 버전 체크

EULA·개인정보처리방침·특정 지역의 약관 개정과 같이 “플레이어가 어느 버전에 동의했는지"를 판정하는 용도로 이용합니다.

AcceptVersion API를 호출함으로써 사용자가 임의의 버전을 승인 상태로 만들 수 있습니다. 클라이언트에서 전송되는 버전이 아니라 서버 측에 기록된 승인 버전이 임계값 판정의 대상이 되므로, 위장을 통해 약관 동의를 회피할 수 없습니다.

스케줄에 의한 임계값 전환

type: schedule을 선택하면 scheduleVersions에 GS2-Schedule의 이벤트ID와 해당 이벤트 기간 중의 버전을 설정할 수 있습니다. 예를 들어 “12월 1일부터는 이용약관 v2를 필수로 한다"와 같은 운영을, 배포 없이 예약할 수 있습니다.

서명 검증

needSignature를 활성화하면 CheckVersion 시 서명이 포함된 버전 정보를 요구할 수 있습니다. 서명은 GS2-Key에서 발급한 키(signatureKeyId)로 검증되므로, 변조된 버전 정보를 배제할 수 있습니다.

스크립트 트리거

네임스페이스에 checkVersionTriggerScriptId·acceptVersionScript를 설정하면 버전 체크 시나 버전 승인 시에 커스텀 스크립트를 실행할 수 있습니다.

설정할 수 있는 주요 이벤트 트리거와 스크립트 설정명은 다음과 같습니다.

  • checkVersionTriggerScriptId: 버전 체크 처리 시 호출되는 스크립트입니다. 동적인 판정 로직을 추가할 수 있습니다.
  • acceptVersionScript: 버전 승인 시 호출되는 스크립트입니다.

트랜잭션 액션

GS2-Version에서는 다음과 같은 트랜잭션 액션을 제공합니다.

입수 액션

액션용도
Gs2Version:AcceptByUserId지정한 버전을 승인 상태로 만듭니다(액티브 버전 체크용).

“지정한 버전의 승인"을 입수 액션으로 이용함으로써, 특정 아이템을 입수했을 때나 미션을 달성했을 때 등에 자동으로 특정 약관이나 버전을 승인 완료 상태로 만드는 처리가 가능해집니다.

마스터 데이터 운용

마스터 데이터를 등록함으로써 마이크로서비스에서 이용 가능한 데이터와 동작을 설정할 수 있습니다.

마스터 데이터의 종류에는 다음이 있습니다.

  • VersionModel: 버전 모델 정의

다음은 마스터 데이터 JSON의 예시입니다.

{
  "version": "2019-08-19",
  "versionModels": [
    {
      "name": "app",
      "metadata": "앱 본체",
      "scope": "passive",
      "type": "simple",
      "warningVersion": { "major": 1, "minor": 2, "micro": 0 },
      "errorVersion":   { "major": 1, "minor": 0, "micro": 0 }
    },
    {
      "name": "eula",
      "metadata": "이용약관",
      "scope": "active",
      "type": "simple",
      "currentVersion": { "major": 1, "minor": 0, "micro": 0 },
      "warningVersion": { "major": 1, "minor": 0, "micro": 0 },
      "errorVersion":   { "major": 1, "minor": 0, "micro": 0 },
      "approveRequirement": "required"
    }
  ]
}

마스터 데이터 등록은 관리 콘솔에서 등록하는 방법 외에도, GitHub에서 데이터를 반영하거나 GS2-Deploy를 사용하여 CI에서 등록하는 워크플로우를 구성할 수 있습니다.

구현 예제

버전 체크 실행

CheckVersion의 결과에는 경고에 해당하는 항목(Warnings)·오류에 해당하는 항목(Errors)·성공 시 발급되는 ProjectToken이 포함됩니다. ProjectToken을 받은 경우에는 이를 사용하여 재인증을 수행함으로써, 실제 게임 플레이에 필요한 권한을 가진 세션을 취득할 수 있습니다.

    var result = await gs2.Version.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Checker(
    ).CheckVersionAsync(
        targetVersions: new [] {
            new Gs2.Unity.Gs2Version.Model.EzTargetVersion
            {
                VersionName = "app",
                Version = new Gs2.Unity.Gs2Version.Model.EzVersion
                {
                    Major = 1,
                    Minor = 2,
                    Micro = 3,
                },
            },
            new Gs2.Unity.Gs2Version.Model.EzTargetVersion
            {
                VersionName = "asset",
                Version = new Gs2.Unity.Gs2Version.Model.EzVersion
                {
                    Major = 1,
                    Minor = 2,
                    Micro = 3,
                },
            },
        }
    );
    var projectToken = result.ProjectToken;
    var warnings = result.Warnings;
    var errors = result.Errors;

    if (errors != null && errors.Count > 0) {
        // 강제 버전 업
    } else if (warnings != null && warnings.Count > 0) {
        // 업데이트 권장 다이얼로그를 표시하면서 계속 진행
    }
    const auto Future = Gs2->Version->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Checker(
    )->CheckVersion(
        []
        {
            const auto v = MakeShared<TArray<TSharedPtr<Gs2::Version::Model::FTargetVersion>>>();
            v->Add({'versionName': 'app', 'version': {'major': 1, 'minor': 2, 'micro': 3}});
            v->Add({'versionName': 'asset', 'version': {'major': 1, 'minor': 2, 'micro': 3}});
            return v;
        }() // targetVersions
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto ProjectToken = Result->ProjectToken;
    const auto Warnings = Result->Warnings;
    const auto Errors = Result->Errors;
var domain = ez.version.namespace_(
        "namespace-0001"
    ).me(game_session).checker(
    )

var async_result = await domain.check_version(
    [
        Gs2VersionEzTargetVersion.new()
            .with_version_name("app")
            .with_version(
            Gs2VersionEzVersion.new()
                .with_major(1)
                .with_minor(2)
                .with_micro(3)
            ),
        Gs2VersionEzTargetVersion.new()
            .with_version_name("asset")
            .with_version(
            Gs2VersionEzVersion.new()
                .with_major(1)
                .with_minor(2)
                .with_micro(3)
            ),
    ] # target_versions
)
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

액티브 버전 체크에 동의

이용약관 동의 버튼을 눌렀을 때 등에 AcceptAsync를 호출합니다. 동의한 버전은 AcceptVersion 모델에 사용자별로 기록되며, 이후의 CheckVersion에서 참조됩니다.

    var result = await gs2.Version.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).AcceptVersion(
        versionName: "eula"
    ).AcceptAsync(
    );
    const auto Future = Gs2->Version->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->AcceptVersion(
        "eula" // versionName
    )->Accept(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
    const auto Result = Future2->GetTask().Result();
var domain = ez.version.namespace_(
        "namespace-0001"
    ).me(game_session).accept_version(
        "eula"
    )

var async_result = await domain.accept(
    (Gs2VersionVersion.new()
        .with_major(2)
        .with_minor(2)
        .with_micro(2)) # version
)
if async_result.error != null:
    if async_result.error.type == "AcceptVersionInvalidException":
        # 승인 프로세스 도중에 서버 버전이 갱신된 결과 오류가 발생했습니다
        pass
    push_error(str(async_result.error))
    return

var result = async_result.result

버전 모델 취득

UI에 “현재 약관 버전”, “경고 대상 임계값"을 표시하기 위해 버전 모델 자체를 취득할 수 있습니다.

    var item = await gs2.Version.Namespace(
        namespaceName: "namespace-0001"
    ).VersionModel(
        versionName: "eula"
    ).ModelAsync();
    const auto Domain = Gs2->Version->Namespace(
        "namespace-0001" // namespaceName
    )->VersionModel(
        "eula" // versionName
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Item = Future->GetTask().Result();
var domain = ez.version.namespace_(
        "namespace-0001"
    ).version_model(
        "version-0001"
    )

var async_result = await domain.model()
if async_result.error != null:
    push_error(str(async_result.error))
    return

var result = async_result.result

보다 실전적인 정보

버전 업데이트 운영 절차

버전 업데이트 시, 플레이 중인 모든 플레이어를 한 번에 내보내고 최신 버전에서만 플레이할 수 있도록 하고 싶을 때가 있습니다.
GS2-Version에서는 신규 로그인을 막을 수는 있지만, 이미 로그인된 플레이어는 버전 체크 후 임시로 발급된 클라이언트ID/클라이언트시크릿의 유효기간이 만료될 때까지 계속 접근할 수 있습니다.

그래서 모든 플레이어의 GS2-Gateway가 제공하는 알림용 상시 접속 세션을 끊고, 게임에서 세션 끊김을 핸들링하면 버전 체크 후 재접속 처리를 수행하도록 합니다.
버전 체크에 실패한 경우에는 그대로 버전 업 시퀀스로 진입합니다.

이렇게 하면 버전을 업데이트하여 플레이 중인 모든 플레이어에게도 버전 체크를 강제할 수 있습니다.

임시 클라이언트ID 활용

ProjectToken을 조합하면, 실행 시 앱에 내장되어 있는 클라이언트ID/시크릿에는 “로그인과 버전 체크만 가능"한 최소한의 권한만 부여해 두고, 버전 체크 통과 후에 얻는 ProjectToken을 통해 게임 플레이에 필요한 강력한 권한을 행사하는 2단계 인증 구성을 취할 수 있습니다. 이를 통해 앱의 리버스 엔지니어링으로 클라이언트ID/시크릿이 유출되더라도, 피해를 버전 체크 돌파 수준으로 한정할 수 있습니다.

상세 레퍼런스