2019년 1월 17일 목요일

DART 언어 googleapis_auth 예제를 통해서 배운 것.

DART 언어 googleapis_auth 패키지 레포에서는 다음과 같은 사용 예제를 제시하고 있다.

import "package:googleapis_auth/auth_browser.dart";

...

var id = new ClientId("....apps.googleusercontent.com", null);
var scopes = [...];

// Initialize the browser oauth2 flow functionality.
createImplicitBrowserFlow(id, scopes).then((BrowserOAuth2Flow flow) {
  flow.obtainAccessCredentialsViaUserConsent()
      .then((AccessCredentials credentials) {
    // Credentials are available in [credentials].
    ...
    flow.close();
  });
});

또는

import "package:googleapis_auth/auth_browser.dart";

...

var id = new ClientId("....apps.googleusercontent.com", null);
var scopes = [...];

// Initialize the browser oauth2 flow functionality.
createImplicitBrowserFlow(id, scopes).then((BrowserOAuth2Flow flow) {
  flow.clientViaUserConsent().then((AuthClient client) {
    // Authenticated and auto refreshing client is available in [client].
    ...
    client.close();
    flow.close();
  });
});

그러면서 obtainAccessCredentialsViaUserConsent, clientViaUserConsent 를 호출할 때는 브라우저의 팝업 방지기능에 걸리지 않도록 event handler에서만 호출할 것을 권하는데, 나는 아래와 같은 코드로 왜 팝업 방지기능에 걸리는지 한참을 헤맸었다.


<material-button (trigger)="onClick">Click me</material-button>


void onClick() {
  createImplicitBrowserFlow(id, scopes).then((BrowserOAuth2Flow flow) {
    flow.clientViaUserConsent().then((AuthClient client) {
      // Authenticated and auto refreshing client is available in [client].
      ...
      client.close();
      flow.close();
    });
  });
}

분명히 onClick 함수 내부에서 clientViaUserConsent 이 호출되는데 무엇이 문제인가? 꽤 오래 고민하다가 stackoverflow에 나와 같은데서 헤매던 사람의 질문에 대한 답을 보고 겨우 깨달았는데, 중간에 .then 문법이 끼어있다는 점이었다. async 메소드가 호출되었으니 then 이하 구절이 onClick과 같은 시점에 호출된다는 보장이 없었던 것.

굳이 적는 이유는 이것이 만약에 async 메소드가 아니라 콜백구조였으면 아마 헤매지 않고  event handler에서 호출되지 않았다는 사실을 알았을 것이라는 점 때문이다. 내가 익숙한 방식이 낡아갈 때가 있고, 이 사건은 그걸 보여주는 것이었기 때문. 돌아가는 코드는 다음과 같은 방식이다.


BrowserOAuth2Flow browserOAuth2Flow = null;
// angulardart 컴퍼넌트의 lifecycle에 따라 호출되는 메소드.
@override
void ngOnInit() {
  // Initialize the browser oauth2 flow functionality.
  createImplicitBrowserFlow(id, scopes).then((BrowserOAuth2Flow flow) {
    browserOAuth2Flow = flow;
  });
}

void onClick() {
  if (browserOAuth2Flow == null) {
    return;
  }
  browserOAuth2Flow
      .obtainAccessCredentialsViaUserConsent()
      .then((AccessCredentials credentials) {
    // Credentials are available in [credentials].
    browserOAuth2Flow.close();
    browserOAuth2Flow = null;
  });
}